Imported Upstream version 1.10.2
authorRafal Krypa <r.krypa@samsung.com>
Tue, 23 Mar 2010 14:25:03 +0000 (15:25 +0100)
committerRafal Krypa <r.krypa@samsung.com>
Tue, 23 Mar 2010 14:25:03 +0000 (15:25 +0100)
1164 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/archive_xread_all_eof.c [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]
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]
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/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/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/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/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/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/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.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_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/crypt_make_salt.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/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/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_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/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/read.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/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/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/xfuncs.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/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]
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/eject.c [new file with mode: 0644]
miscutils/hdparm.c [new file with mode: 0644]
miscutils/last.c [new file with mode: 0644]
miscutils/less.c [new file with mode: 0644]
miscutils/makedevs.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/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/insmod.c [new file with mode: 0644]
modutils/lsmod.c [new file with mode: 0644]
modutils/modprobe.c [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/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/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/sendmail.c [new file with mode: 0644]
networking/slattach.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/tftp.c [new file with mode: 0644]
networking/traceroute.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/find_bad_common_bufsiz [new file with mode: 0755]
scripts/find_stray_common_vars [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/mkconfigs [new file with mode: 0755]
scripts/mkmakefile [new file with mode: 0755]
scripts/objsizes [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-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-redir/redir.right [new file with mode: 0644]
shell/ash_test/ash-redir/redir.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-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/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: 0644]
shell/hush_test/hush-bugs/quote3.right [new file with mode: 0644]
shell/hush_test/hush-bugs/quote3.tests [new file with mode: 0755]
shell/hush_test/hush-bugs/tick.right [new file with mode: 0644]
shell/hush_test/hush-bugs/tick.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/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-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/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/quote4.right [new file with mode: 0644]
shell/hush_test/hush-parsing/quote4.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-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/var.right [new file with mode: 0644]
shell/hush_test/hush-vars/var.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_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_var.right [new file with mode: 0644]
shell/hush_test/hush-z_slow/leak_var.tests [new file with mode: 0755]
shell/hush_test/run-all [new file with mode: 0755]
shell/hush_test/zbad [new file with mode: 0644]
shell/hush_test/zbad2 [new file with mode: 0644]
shell/lash_unused.c [new file with mode: 0644]
shell/msh.c [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/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]
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/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/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-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/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/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/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/expand/expand-works-like-GNU [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/md5sum/md5sum-verifies-non-binary-file [new file with mode: 0644]
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/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/pidof.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/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/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/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/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/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/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/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/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..9755ad9
--- /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 Fischer <rep.nop@aon.at>
+    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..a3354eb
--- /dev/null
+++ b/Config.in
@@ -0,0 +1,569 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+mainmenu "BusyBox Configuration"
+
+config HAVE_DOT_CONFIG
+       bool
+       default y
+
+menu "Busybox Settings"
+
+menu "General Configuration"
+
+config NITPICK
+       bool "See lots more (probably unnecessary) configuration options."
+       default n
+       help
+         Some BusyBox applets have more configuration options than anyone
+         will ever care about.  To avoid drowining people in complexity, most
+         of the applet features that can be set to a sane default value are
+         hidden, unless you hit the above switch.
+
+         This is better than to telling people to edit the busybox source
+         code, but not by much.
+
+         See http://en.wikipedia.org/wiki/Fibber_McGee_and_Molly#The_Closet
+
+         You have been warned.
+
+config DESKTOP
+       bool "Enable options for full-blown desktop systems"
+       default n
+       help
+         Enable options and features which are not essential.
+         Select this only if you plan to use busybox on full-blown
+         desktop machine with common Linux distro, not on an embedded box.
+
+choice
+       prompt "Buffer allocation policy"
+       default FEATURE_BUFFERS_USE_MALLOC
+       depends on NITPICK
+       help
+         There are 3 ways BusyBox can handle buffer allocations:
+         - Use malloc. This costs code size for the call to xmalloc.
+         - Put them on stack. For some very small machines with limited stack
+           space, this can be deadly.  For most folks, this works just fine.
+         - Put them in BSS. This works beautifully for computers with a real
+           MMU (and OS support), but wastes runtime RAM for uCLinux. This
+           behavior was the only one available for BusyBox versions 0.48 and
+           earlier.
+
+config FEATURE_BUFFERS_USE_MALLOC
+       bool "Allocate with Malloc"
+
+config FEATURE_BUFFERS_GO_ON_STACK
+       bool "Allocate on the Stack"
+
+config FEATURE_BUFFERS_GO_IN_BSS
+       bool "Allocate in the .bss section"
+
+endchoice
+
+config SHOW_USAGE
+       bool "Show terse applet usage messages"
+       default y
+       help
+         All BusyBox applets will show help messages when invoked with
+         wrong arguments. You can turn off printing these terse usage
+         messages if you say no here.
+         This will save you up to 7k.
+
+config FEATURE_VERBOSE_USAGE
+       bool "Show verbose applet usage messages"
+       default n
+       select SHOW_USAGE
+       help
+         All BusyBox applets will show more verbose help messages when
+         busybox is invoked with --help.  This will add a lot of text to the
+         busybox binary.  In the default configuration, this will add about
+         13k, but it can add much more depending on your configuration.
+
+config FEATURE_COMPRESS_USAGE
+       bool "Store applet usage messages in compressed form"
+       default y
+       depends on SHOW_USAGE
+       help
+         Store usage messages in compressed form, uncompress them on-the-fly
+         when <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
+       depends on NITPICK
+       help
+         As a size optimization, busybox normally exits without explicitly
+         freeing dynamically allocated memory or closing files.  This saves
+         space since the OS will clean up for us, but it can confuse debuggers
+         like valgrind, which report tons of memory and resource leaks.
+
+         Don't enable this unless you have a really good reason to clean
+         things up manually.
+
+config FEATURE_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'll and it'll automatically drop
+         priviledges for applets that don't need root access.
+
+         If you're really paranoid and don't want to do this, build two
+         busybox binaries with different applets in them (and the appropriate
+         symlinks pointing to each binary), and only set the suid bit on the
+         one that needs it.  The applets currently marked to need the suid bit
+         are login, passwd, su, ping, traceroute, crontab, dnsd, ipcrm, ipcs,
+         and vlock.
+
+config FEATURE_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 "Support for logging to syslog"
+       default n
+       help
+         This option is auto-selected when you select any applet which may
+         send its output to syslog. You do not need to select it manually.
+
+config FEATURE_HAVE_RPC
+       bool "RPC support"
+       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 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'.
+
+config BUILD_LIBBUSYBOX
+       bool "Build shared libbusybox"
+       default n
+       depends on !FEATURE_PREFER_APPLETS
+       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 !STATIC && 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 !STATIC && 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'.
+
+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 WERROR
+       bool "Abort compilation on any warning"
+       default n
+       help
+         Selecting this will add -Werror to gcc command line.
+
+         Most people should answer N.
+
+# Seems to be unused
+#config DEBUG_PESSIMIZE
+#      bool "Disable compiler optimizations."
+#      default n
+#      depends on DEBUG
+#      help
+#        The compiler's optimization of source code can eliminate and reorder
+#        code, resulting in an executable that's hard to understand when
+#        stepping through it with a debugger.  This switches it off, resulting
+#        in a much bigger executable that more closely matches the source
+#        code.
+
+choice
+       prompt "Additional debugging library"
+       default NO_DEBUG_LIB
+       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.)
+
+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 procps/Config.in
+source shell/Config.in
+source sysklogd/Config.in
+source runit/Config.in
+source selinux/Config.in
+source printutils/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..4feab0e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,1297 @@
+VERSION = 1
+PATCHLEVEL = 10
+SUBLEVEL = 2
+EXTRAVERSION =
+NAME = Unnamed
+
+# *DOCUMENTATION*
+# To see a list of typical targets execute "make help"
+# More info can be located in ./README
+# Comments in this file are targeted only to the developer, do not
+# expect to learn how to build the kernel reading this file.
+
+# Do not print "Entering directory ..."
+MAKEFLAGS += --no-print-directory
+
+# We are using a recursive build, so we need to do a little thinking
+# to get the ordering right.
+#
+# Most importantly: sub-Makefiles should only ever modify files in
+# their own directory. If in some directory we have a dependency on
+# a file in another dir (which doesn't happen often, but it's often
+# unavoidable when linking the built-in.o targets which finally
+# turn into busybox), we will call a sub make in that other dir, and
+# after that we are sure that everything which is in that other dir
+# is now up to date.
+#
+# The only cases where we need to modify files which have global
+# effects are thus separated out and done before the recursive
+# descending is started. They are now explicitly listed as the
+# prepare rule.
+
+# To put more focus on warnings, be less verbose as default
+# Use 'make V=1' to see the full commands
+
+ifdef V
+  ifeq ("$(origin V)", "command line")
+    KBUILD_VERBOSE = $(V)
+  endif
+endif
+ifndef KBUILD_VERBOSE
+  KBUILD_VERBOSE = 0
+endif
+
+# Call sparse as part of compilation of C files
+# Use 'make C=1' to enable sparse checking
+
+ifdef C
+  ifeq ("$(origin C)", "command line")
+    KBUILD_CHECKSRC = $(C)
+  endif
+endif
+ifndef KBUILD_CHECKSRC
+  KBUILD_CHECKSRC = 0
+endif
+
+# Use make M=dir to specify directory of external module to build
+# Old syntax make ... SUBDIRS=$PWD is still supported
+# Setting the environment variable KBUILD_EXTMOD take precedence
+ifdef SUBDIRS
+  KBUILD_EXTMOD ?= $(SUBDIRS)
+endif
+ifdef M
+  ifeq ("$(origin M)", "command line")
+    KBUILD_EXTMOD := $(M)
+  endif
+endif
+
+
+# kbuild supports saving output files in a separate directory.
+# To locate output files in a separate directory two syntaxes are supported.
+# In both cases the working directory must be the root of the kernel src.
+# 1) O=
+# Use "make O=dir/to/store/output/files/"
+#
+# 2) Set KBUILD_OUTPUT
+# Set the environment variable KBUILD_OUTPUT to point to the directory
+# where the output files shall be placed.
+# export KBUILD_OUTPUT=dir/to/store/output/files/
+# make
+#
+# The O= assignment takes precedence over the KBUILD_OUTPUT environment
+# variable.
+
+
+# KBUILD_SRC is set on invocation of make in OBJ directory
+# KBUILD_SRC is not intended to be used by the regular user (for now)
+ifeq ($(KBUILD_SRC),)
+
+# OK, Make called in directory where kernel src resides
+# Do we want to locate output files in a separate directory?
+ifdef O
+  ifeq ("$(origin O)", "command line")
+    KBUILD_OUTPUT := $(O)
+  endif
+endif
+
+# That's our default target when none is given on the command line
+PHONY := _all
+_all:
+
+ifneq ($(KBUILD_OUTPUT),)
+# Invoke a second make in the output directory, passing relevant variables
+# check that the output directory actually exists
+saved-output := $(KBUILD_OUTPUT)
+KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd)
+$(if $(KBUILD_OUTPUT),, \
+     $(error output directory "$(saved-output)" does not exist))
+
+PHONY += $(MAKECMDGOALS)
+
+$(filter-out _all,$(MAKECMDGOALS)) _all:
+       $(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \
+       KBUILD_SRC=$(CURDIR) \
+       KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile $@
+
+# Leave processing to above invocation of make
+skip-makefile := 1
+endif # ifneq ($(KBUILD_OUTPUT),)
+endif # ifeq ($(KBUILD_SRC),)
+
+# We process the rest of the Makefile if this is the final invocation of make
+ifeq ($(skip-makefile),)
+
+# If building an external module we do not care about the all: rule
+# but instead _all depend on modules
+PHONY += all
+ifeq ($(KBUILD_EXTMOD),)
+_all: all
+else
+_all: modules
+endif
+
+srctree                := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
+TOPDIR         := $(srctree)
+# FIXME - TOPDIR is obsolete, use srctree/objtree
+objtree                := $(CURDIR)
+src            := $(srctree)
+obj            := $(objtree)
+
+VPATH          := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
+
+export srctree objtree VPATH TOPDIR
+
+
+# SUBARCH tells the usermode build what the underlying arch is.  That is set
+# first, and if a usermode build is happening, the "ARCH=um" on the command
+# line overrides the setting of ARCH below.  If a native build is happening,
+# then ARCH is assigned, getting whatever value it gets normally, and
+# SUBARCH is subsequently ignored.
+
+SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
+                                 -e s/arm.*/arm/ -e s/sa110/arm/ \
+                                 -e s/s390x/s390/ -e s/parisc64/parisc/ \
+                                 -e s/ppc.*/powerpc/ -e s/mips.*/mips/ )
+
+# Cross compiling and selecting different set of gcc/bin-utils
+# ---------------------------------------------------------------------------
+#
+# When performing cross compilation for other architectures ARCH shall be set
+# to the target architecture. (See arch/* for the possibilities).
+# ARCH can be set during invocation of make:
+# make ARCH=ia64
+# Another way is to have ARCH set in the environment.
+# The default ARCH is the host where make is executed.
+
+# CROSS_COMPILE specify the prefix used for all executables used
+# during compilation. Only gcc and related bin-utils executables
+# are prefixed with $(CROSS_COMPILE).
+# CROSS_COMPILE can be set on the command line
+# make CROSS_COMPILE=ia64-linux-
+# Alternatively CROSS_COMPILE can be set in the environment.
+# Default value for CROSS_COMPILE is not to prefix executables
+# Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile
+
+ARCH           ?= $(SUBARCH)
+CROSS_COMPILE  ?=
+
+# Architecture as present in compile.h
+UTS_MACHINE := $(ARCH)
+
+# SHELL used by kbuild
+CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
+         else if [ -x /bin/bash ]; then echo /bin/bash; \
+         else echo sh; fi ; fi)
+
+#      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)
+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 check%
+
+config-targets := 0
+mixed-targets  := 0
+dot-config     := 1
+
+ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
+       ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
+               dot-config := 0
+       endif
+endif
+
+ifeq ($(KBUILD_EXTMOD),)
+        ifneq ($(filter config %config,$(MAKECMDGOALS)),)
+                config-targets := 1
+                ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
+                        mixed-targets := 1
+                endif
+        endif
+endif
+
+ifeq ($(mixed-targets),1)
+# ===========================================================================
+# We're called with mixed targets (*config and build targets).
+# Handle them one by one.
+
+%:: FORCE
+       $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@
+
+else
+ifeq ($(config-targets),1)
+# ===========================================================================
+# *config targets only - make sure prerequisites are updated, and descend
+# in scripts/kconfig to make the *config target
+
+# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
+# KBUILD_DEFCONFIG may point out an alternative default configuration
+# used for 'make defconfig'
+-include $(srctree)/arch/$(ARCH)/Makefile
+export KBUILD_DEFCONFIG
+
+config %config: scripts_basic outputmakefile FORCE
+       $(Q)mkdir -p include
+       $(Q)$(MAKE) $(build)=scripts/kconfig $@
+       $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= .kernelrelease
+
+else
+# ===========================================================================
+# Build targets only - this includes busybox, arch specific targets, clean
+# targets and others. In general all targets except *config targets.
+
+ifeq ($(KBUILD_EXTMOD),)
+# Additional helpers built in scripts/
+# Carefully list dependencies so we do not try to build scripts twice
+# in parrallel
+PHONY += scripts
+scripts: scripts_basic include/config/MARKER
+       $(Q)$(MAKE) $(build)=$(@)
+
+scripts_basic: include/autoconf.h
+
+# Objects we will link into busybox / subdirs we need to visit
+core-y         := \
+               applets/ \
+
+libs-y         := \
+               archival/ \
+               archival/libunarchive/ \
+               console-tools/ \
+               coreutils/ \
+               coreutils/libcoreutils/ \
+               debianutils/ \
+               e2fsprogs/ \
+               editors/ \
+               findutils/ \
+               init/ \
+               libbb/ \
+               libpwdgrp/ \
+               loginutils/ \
+               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
+
+-include $(srctree)/arch/$(ARCH)/Makefile
+
+# arch Makefile may override CC so keep this after arch Makefile is included
+#bbox# NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include)
+CHECKFLAGS += $(NOSTDINC_FLAGS)
+
+# warn about C99 declaration after statement
+CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)
+
+# disable pointer signedness warnings in gcc 4.0
+CFLAGS += $(call cc-option,-Wno-pointer-sign,)
+
+# Default kernel image to build when no specific target is given.
+# KBUILD_IMAGE may be overruled on the commandline or
+# set in the environment
+# Also any assignments in arch/$(ARCH)/Makefile take precedence over
+# this default value
+export KBUILD_IMAGE ?= busybox
+
+#
+# INSTALL_PATH specifies where to place the updated kernel and system map
+# images. Default is /boot, but you can set it to other values
+export INSTALL_PATH ?= /boot
+
+#
+# INSTALL_MOD_PATH specifies a prefix to MODLIB for module directory
+# relocations required by build roots.  This is not defined in the
+# makefile but the arguement can be passed to make if needed.
+#
+
+MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
+export MODLIB
+
+
+ifeq ($(KBUILD_EXTMOD),)
+busybox-dirs   := $(patsubst %/,%,$(filter %/, $(core-y) $(core-m) $(libs-y) $(libs-m)))
+
+busybox-alldirs        := $(sort $(busybox-dirs) $(patsubst %/,%,$(filter %/, \
+                    $(core-n) $(core-) $(libs-n) $(libs-) \
+               )))
+
+core-y         := $(patsubst %/, %/built-in.o, $(core-y))
+libs-y1                := $(patsubst %/, %/lib.a, $(libs-y))
+libs-y2                := $(patsubst %/, %/built-in.o, $(libs-y))
+libs-y         := $(libs-y1) $(libs-y2)
+
+# Build busybox
+# ---------------------------------------------------------------------------
+# busybox is build from the objects selected by $(busybox-init) and
+# $(busybox-main). Most are built-in.o files from top-level directories
+# in the kernel tree, others are specified in arch/$(ARCH)Makefile.
+# Ordering when linking is important, and $(busybox-init) must be first.
+#
+# busybox
+#   ^
+#   |
+#   +-< $(busybox-init)
+#   |   +--< init/version.o + more
+#   |
+#   +--< $(busybox-main)
+#   |    +--< driver/built-in.o mm/built-in.o + more
+#   |
+#   +-< kallsyms.o (see description in CONFIG_KALLSYMS section)
+#
+# busybox version (uname -v) cannot be updated during normal
+# descending-into-subdirs phase since we do not yet know if we need to
+# update busybox.
+# Therefore this step is delayed until just before final link of busybox -
+# except in the kallsyms case where it is done just before adding the
+# symbols to the kernel.
+#
+# System.map is generated to document addresses of all kernel symbols
+
+busybox-all  := $(core-y) $(libs-y)
+
+# Rule to link busybox - also used during CONFIG_KALLSYMS
+# May be overridden by arch/$(ARCH)/Makefile
+quiet_cmd_busybox__ ?= LINK    $@
+      cmd_busybox__ ?= $(srctree)/scripts/trylink \
+      "$@" \
+      "$(CC)" \
+      "$(CFLAGS)" \
+      "$(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 $@
+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)
+CLEAN_FILES += busybox* 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*
+
+# clean - Delete most, but leave enough to build external modules
+#
+clean: rm-dirs  := $(CLEAN_DIRS)
+clean: rm-files := $(CLEAN_FILES)
+clean-dirs      := $(addprefix _clean_,$(srctree) $(busybox-alldirs))
+
+PHONY += $(clean-dirs) clean archclean
+$(clean-dirs):
+       $(Q)$(MAKE) $(clean)=$(patsubst _clean_%,%,$@)
+
+clean: archclean $(clean-dirs)
+       $(call cmd,rmdirs)
+       $(call cmd,rmfiles)
+       @find . $(RCS_FIND_IGNORE) \
+               \( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \
+               -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \) \
+               -type f -print | xargs rm -f
+
+# mrproper - Delete all generated files, including .config
+#
+mrproper: rm-dirs  := $(wildcard $(MRPROPER_DIRS))
+mrproper: rm-files := $(wildcard $(MRPROPER_FILES))
+mrproper-dirs      := $(addprefix _mrproper_,scripts)
+
+PHONY += $(mrproper-dirs) mrproper archmrproper
+$(mrproper-dirs):
+       $(Q)$(MAKE) $(clean)=$(patsubst _mrproper_%,%,$@)
+
+mrproper: clean archmrproper $(mrproper-dirs)
+       $(call cmd,rmdirs)
+       $(call cmd,rmfiles)
+
+# distclean
+#
+PHONY += distclean
+
+distclean: mrproper
+       @find $(srctree) $(RCS_FIND_IGNORE) \
+               \( -name '*.orig' -o -name '*.rej' -o -name '*~' \
+               -o -name '*.bak' -o -name '#*#' -o -name '.*.orig' \
+               -o -name '.*.rej' -o -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..a4db141
--- /dev/null
@@ -0,0 +1,160 @@
+# ==========================================================================
+# 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
+
+check test: busybox busybox.links
+       bindir=$(objtree) srcdir=$(srctree)/testsuite SED="$(SED)" \
+       $(SHELL) $(srctree)/testsuite/runtest $(if $(KBUILD_VERBOSE:0=),-v)
+
+.PHONY: release
+release: distclean
+       cd ..; \
+       rm -r -f busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION); \
+       cp -a busybox busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) && { \
+       find busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ -type d \
+               -name .svn \
+               -print \
+               -exec rm -r -f {} \; ; \
+       find busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ -type f \
+               -name .\#* \
+               -print \
+               -exec rm -f {} \; ; \
+       tar -czf busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION).tar.gz \
+               busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ ; }
+
+.PHONY: checkhelp
+checkhelp:
+       $(Q)$(srctree)/scripts/checkhelp.awk \
+               $(patsubst %,$(srctree)/%,$(wildcard $(patsubst %,%/Config.in,$(busybox-dirs) ./)))
+
+.PHONY: sizes
+sizes: busybox_unstripped
+       $(NM) --size-sort $(<)
+
+.PHONY: bloatcheck
+bloatcheck: busybox_old busybox_unstripped
+       @$(srctree)/scripts/bloat-o-meter busybox_old busybox_unstripped
+       @$(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 ' [tr] '
+
+# Documentation Targets
+.PHONY: doc
+doc: docs/busybox.pod docs/BusyBox.txt docs/BusyBox.1 docs/BusyBox.html
+
+docs/busybox.pod: $(srctree)/docs/busybox_header.pod \
+               $(srctree)/include/usage.h \
+               $(srctree)/docs/busybox_footer.pod \
+               $(srctree)/docs/autodocifier.pl
+       $(disp_doc)
+       $(Q)-mkdir -p docs
+       $(Q)-( cat $(srctree)/docs/busybox_header.pod ; \
+           $(srctree)/docs/autodocifier.pl $(srctree)/include/usage.h ; \
+           cat $(srctree)/docs/busybox_footer.pod ; ) > docs/busybox.pod
+
+docs/BusyBox.txt: docs/busybox.pod
+       $(disp_doc)
+       $(Q)-mkdir -p docs
+       $(Q)-pod2text $< > $@
+
+docs/BusyBox.1: docs/busybox.pod
+       $(disp_doc)
+       $(Q)-mkdir -p docs
+       $(Q)-pod2man --center=BusyBox --release="version $(VERSION)" \
+               $< > $@
+
+docs/BusyBox.html: docs/busybox.net/BusyBox.html
+       $(disp_doc)
+       $(Q)-mkdir -p docs
+       $(Q)-rm -f docs/BusyBox.html
+       $(Q)-cp docs/busybox.net/BusyBox.html docs/BusyBox.html
+
+docs/busybox.net/BusyBox.html: docs/busybox.pod
+       $(Q)-mkdir -p docs/busybox.net
+       $(Q)-pod2html --noindex $< > \
+           docs/busybox.net/BusyBox.html
+       $(Q)-rm -f pod2htm*
+
+# documentation, cross-reference
+# Modern distributions already ship synopsis packages (e.g. debian)
+# If you have an old distribution go to http://synopsis.fresco.org/
+syn_tgt = $(wildcard $(patsubst %,%/*.c,$(busybox-alldirs)))
+syn     = $(patsubst %.c, %.syn, $(syn_tgt))
+
+comma:= ,
+brace_open:= (
+brace_close:= )
+
+SYN_CPPFLAGS := $(strip $(CPPFLAGS) $(EXTRA_CPPFLAGS))
+SYN_CPPFLAGS := $(subst $(brace_open),\$(brace_open),$(SYN_CPPFLAGS))
+SYN_CPPFLAGS := $(subst $(brace_close),\$(brace_close),$(SYN_CPPFLAGS))
+#SYN_CPPFLAGS := $(subst ",\",$(SYN_CPPFLAGS))
+#")
+#SYN_CPPFLAGS := [$(patsubst %,'%'$(comma),$(SYN_CPPFLAGS))'']
+
+%.syn: %.c
+       synopsis -p C -l Comments.SSDFilter,Comments.Previous -Wp,preprocess=True,cppflags="'$(SYN_CPPFLAGS)'" -o $@ $<
+
+.PHONY: html
+html: $(syn)
+       synopsis -f HTML -Wf,title="'BusyBox Documentation'" -o $@ $^
+
+-include $(srctree)/Makefile.local
diff --git a/Makefile.flags b/Makefile.flags
new file mode 100644 (file)
index 0000000..61bff4f
--- /dev/null
@@ -0,0 +1,102 @@
+# ==========================================================================
+# 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
+
+# flag checks are grouped together to speed the checks up a bit..
+CFLAGS += $(call cc-option,-Wall -Wshadow -Wwrite-strings,)
+CFLAGS += $(call cc-option,-Wundef -Wstrict-prototypes,)
+CFLAGS += $(call cc-option,-Wunused -Wunused-parameter,)
+# If you want to add "-Wmissing-prototypes -Wmissing-declarations" above
+# (or anything else for that matter) make sure that it is still possible
+# to build bbox without warnings. Current offender: find.c:alloc_action().
+# Looks more like gcc bug: gcc will warn on it with or without prototype.
+# But still, warning-free compile is a must, or else we will drown
+# in warnings pretty soon.
+
+ifeq ($(CONFIG_WERROR),y)
+CFLAGS += $(call cc-option,-Werror,)
+else
+# for development, warn a little bit about unused results..
+CPPFLAGS += -D_FORTIFY_SOURCE=2
+endif
+# gcc 3.x emits bogus "old style proto" warning on find.c:alloc_action()
+CFLAGS += $(call cc-ifversion, -ge, 0400, -Wold-style-definition)
+
+# gcc emits bogus "no prev proto" warning on find.c:alloc_action()
+ifneq ($(CONFIG_WERROR),y)
+CFLAGS += $(call cc-option,-Wmissing-prototypes -Wmissing-declarations,)
+endif
+
+CFLAGS += $(call cc-option,-Os -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,)
+
+ifeq ($(CONFIG_DEBUG),y)
+CFLAGS += $(call cc-option,-g)
+endif
+
+ifeq ($(CONFIG_BUILD_LIBBUSYBOX),y)
+# on i386: 14% smaller libbusybox.so
+# (code itself is 9% bigger, we save on relocs/PLT/GOT)
+CFLAGS += -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)
+LDFLAGS += -static
+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
+
+#LDFLAGS += -nostdlib
+
+LDFLAGS_ELF2FLT = -Wl,-elf2flt
+ifneq (,$(findstring $(LDFLAGS_ELF2FLT),$(LDFLAGS)))
+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..f957403
--- /dev/null
@@ -0,0 +1,43 @@
+# ==========================================================================
+# Build system
+# ==========================================================================
+
+help:
+       @echo 'Cleaning:'
+       @echo '  clean                  - delete temporary files created by build'
+       @echo '  distclean              - delete all non-source files (including .config)'
+       @echo
+       @echo 'Build:'
+       @echo '  all                    - Executable and documentation'
+       @echo '  busybox                - the swiss-army executable'
+       @echo '  doc                    - docs/BusyBox.{txt,html,1}'
+       @echo '  html                   - create html-based cross-reference'
+       @echo
+       @echo 'Configuration:'
+       @echo '  allnoconfig            - disable all symbols in .config'
+       @echo '  allyesconfig           - enable all symbols in .config (see defconfig)'
+       @echo '  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..24a26ed
--- /dev/null
+++ b/README
@@ -0,0 +1,201 @@
+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
+  (http://bugs.busybox.net) although posting a bug/patch to the mailing list
+  is generally a faster way of getting it fixed, and the complete archive of
+  what happened is the subversion changelog.
+
+  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.  thanks for the wonderful program!
+
+       -diligent
+
+  note the careful description and use of examples showing not only what
+  busybox does, but also a counter example showing what an equivalent app
+  does (or pointing to the text of a relevant standard).  Bug reports lacking
+  such detail may never be fixed...  Thanks for understanding.
+
+----------------
+
+Portability:
+
+  Busybox is developed and tested on Linux 2.4 and 2.6 kernels, compiled
+  with gcc (the unit-at-a-time optimizations in version 3.4 and later are
+  worth upgrading to get, but older versions should work), and linked against
+  uClibc (0.9.27 or greater) or glibc (2.2 or greater).  In such an
+  environment, the full set of busybox features should work, and if
+  anything doesn't we want to know about it so we can fix it.
+
+  There are many other environments out there, in which busybox may build
+  and run just fine.  We just don't test them.  Since busybox consists of a
+  large number of more or less independent applets, portability is a question
+  of which features work where.  Some busybox applets (such as cat and rm) are
+  highly portable and likely to work just about anywhere, while others (such as
+  insmod and losetup) require recent Linux kernels with recent C libraries.
+
+  Earlier versions of Linux and glibc may or may not work, for any given
+  configuration.  Linux 2.2 or earlier should mostly work (there's still
+  some support code in things like mount.c) but this is no longer regularly
+  tested, and inherently won't support certain features (such as long files
+  and --bind mounts).  The same is true for glibc 2.0 and 2.1: expect a higher
+  testing and debugging burden using such old infrastructure.  (The busybox
+  developers are not very interested in supporting these older versions, but
+  will probably accept small self-contained patches to fix simple problems.)
+
+  Some environments are not recommended.  Early versions of uClibc were buggy
+  and missing many features: upgrade.  Linking against libc5 or dietlibc is
+  not supported and not interesting to the busybox developers.  (The first is
+  obsolete and has no known size or feature advantages over uClibc, the second
+  has known bugs that its developers have actively refused to fix.)  Ancient
+  Linux kernels (2.0.x and earlier) are similarly uninteresting.
+
+  In theory it's possible to use Busybox under other operating systems (such as
+  MacOS X, Solaris, Cygwin, or the BSD Fork Du Jour).  This generally involves
+  a different kernel and a different C library at the same time.  While it
+  should be possible to port the majority of the code to work in one of
+  these environments, don't be suprised if it doesn't work out of the box.  If
+  you're into that sort of thing, start small (selecting just a few applets)
+  and work your way up.
+
+  Shaun Jackman has recently (2005) ported busybox to a combination of newlib
+  and libgloss, and some of his patches have been integrated.  This platform
+  may join glibc/uclibc and Linux as a supported combination with the 1.1
+  release, but is not supported in 1.0.
+
+Supported hardware:
+
+  BusyBox in general will build on any architecture supported by gcc.  We
+  support both 32 and 64 bit platforms, and both big and little endian
+  systems.
+
+  Under 2.4 Linux kernels, kernel module loading was implemented in a
+  platform-specific manner.  Busybox's insmod utility has been reported to
+  work under ARM, CRIS, H8/300, x86, ia64, x86_64, m68k, MIPS, PowerPC, S390,
+  SH3/4/5, Sparc, v850e, and x86_64.  Anything else probably won't work.
+
+  The module loading mechanism for the 2.6 kernel is much more generic, and
+  we believe 2.6.x kernel module loading support should work on all
+  architectures supported by the kernel.
+
+----------------
+
+Please feed suggestions, bug reports, insults, and bribes back to the busybox
+maintainer:
+       Denis Vlasenko
+        <vda.linux@googlemail.com>
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..fa9a207
--- /dev/null
+++ b/TODO
@@ -0,0 +1,305 @@
+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 <rob@landley.net>:
+  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 and ENABLE_FEATURE_INIT_DEBUG).
+  depmod
+    busybox lacks a way to update module deps when running from firmware without the
+    use of the depmod.pl (perl is to bloated for most embedded setups) and or orig
+    modutils. The orig depmod is rather pointless to have to add to a firmware image
+    in when we already have a insmod/rmmod and friends.
+  Do a SUSv3 audit
+    Look at the full Single Unix Specification version 3 (available online at
+    "http://www.opengroup.org/onlinepubs/009695399/nfindex.html") and
+    figure out which of our apps are compliant, and what we're missing that
+    we might actually care about.
+
+    Even better would be some kind of automated compliance test harness that
+    exercises each command line option and the various corner cases.
+  Internationalization
+    How much internationalization should we do?
+
+    The low hanging fruit is UTF-8 character set support.  We should do this.
+    (Vodz pointed out the shell's cmdedit as needing work here.  What else?)
+
+    We also have lots of hardwired english text messages.  Consolidating this
+    into some kind of message table not only makes translation easier, but
+    also allows us to consolidate redundant (or close) strings.
+
+    We probably don't want to be bloated with locale support.  (Not unless we
+    can cleanly export it from our underlying C library without having to
+    concern ourselves with it directly.  Perhaps a few specific things like a
+    config option for "date" are low hanging fruit here?)
+
+    What level should things happen at?  How much do we care about
+    internationalizing the text console when X11 and xterms are so much better
+    at it?  (There's some infrastructure here we don't implement: The
+    "unicode_start" and "unicode_stop" shell scripts need "vt-is-UTF8" and a
+    --unicode option to loadkeys.  That implies a real loadkeys/dumpkeys
+    implementation to replace loadkmap/dumpkmap.  Plus messing with console font
+    loading.  Is it worth it, or do we just say "use X"?)
+
+  Individual compilation of applets.
+    It would be nice if busybox had the option to compile to individual applets,
+    for people who want an alternate implementation less bloated than the gnu
+    utils (or simply with less political baggage), but without it being one big
+    executable.
+
+    Turning libbb into a real dll is another possibility, especially if libbb
+    could export some of the other library interfaces we've already more or less
+    got the code for (like zlib).
+  buildroot - Make a "dogfood" option
+    Busybox 1.1 will be capable of replacing most gnu packages for real world
+    use, such as developing software or in a live CD.  It needs wider testing.
+
+    Busybox should now be able to replace bzip2, coreutils, e2fsprogs, file,
+    findutils, gawk, grep, inetutils, less, modutils, net-tools, patch, procps,
+    sed, shadow, sysklogd, sysvinit, tar, util-linux, and vim.  The resulting
+    system should be self-hosting (I.E. able to rebuild itself from source
+    code).  This means it would need (at least) binutils, gcc, and make, or
+    equivalents.
+
+    It would be a good "eating our own dogfood" test if buildroot had the option
+    of using a "make allyesconfig" busybox instead of the all of the above
+    packages.  Anything that's wrong with the resulting system, we can fix.  (It
+    would be nice to be able to upgrade busybox to be able to replace bash and
+    diffutils as well, but we're not there yet.)
+
+    One example of an existing system that does this already is Firmware Linux:
+      http://www.landley.net/code/firmware
+  initramfs
+    Busybox should have a sample initramfs build script.  This depends on
+    bbsh, mdev, and switch_root.
+  mkdep
+    Write a mkdep that doesn't segfault if there's a directory it doesn't
+    have permission to read, isn't based on manually editing the output of
+    lexx and yacc, doesn't make such a mess under include/config, etc.
+  Group globals into unions of structures.
+    Go through and turn all the global and static variables into structures,
+    and have all those structures be in a big union shared between processes,
+    so busybox uses less bss.  (This is a big win on nommu machines.)  See
+    sed.c and mdev.c for examples.
+  Go through bugs.busybox.net and close out all of that somehow.
+    This one's open to everybody, but I'll wind up doing it...
+
+
+Bernhard Fischer <busybox@busybox.net> suggests to look at these:
+  New debug options:
+    -Wlarger-than-127
+    Cleanup any big users
+    -Wunused-parameter
+    Facilitate applet PROTOTYPES to provide means for having applets that
+    do a) not take any arguments b) need only one of argc or argv c) need
+    both argc and argv. All of these three options should go for the most
+    feature complete denominator.
+  Collate BUFSIZ IOBUF_SIZE MY_BUF_SIZE PIPE_PROGRESS_SIZE BUFSIZE PIPESIZE
+    make bb_common_bufsiz1 configurable, size wise.
+    make pipesize configurable, size wise.
+    Use bb_common_bufsiz1 throughout applets!
+
+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
+---
+man
+  It would be nice to have a man command.  Not one that handles troff or
+  anything, just one that can handle preformatted ascii man pages, possibly
+  compressed.  This could probably be a script in the extras directory that
+  calls cat/zcat/bzcat | less
+
+  (How doclifter might work into this is anybody's guess.)
+---
+ar
+  Write support?
+---
+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.  There might be an opportunity
+  for shared code in there that could be moved into libbb...
+---
+Memory Allocation
+  We have a CONFIG_BUFFER mechanism that lets us select whether to do memory
+  allocation on the stack or the heap.  Unfortunately, we're not using it much.
+  We need to audit our memory allocations and turn a lot of malloc/free calls
+  into RESERVE_CONFIG_BUFFER/RELEASE_CONFIG_BUFFER.
+  For a start, see e.g. make EXTRA_CFLAGS=-Wlarger-than-64
+
+  And while we're at it, many of the CONFIG_FEATURE_CLEAN_UP #ifdefs will be
+  optimized out by the compiler in the stack allocation case (since there's no
+  free for an alloca()), and this means that various cleanup loops that just
+  call free might also be optimized out by the compiler if written right, so
+  we can yank those #ifdefs too, and generally clean up the code.
+---
+Switch CONFIG_SYMBOLS to ENABLE_SYMBOLS
+
+  In busybox 1.0 and earlier, configuration was done by CONFIG_SYMBOLS
+  that were either defined or undefined to indicate whether the symbol was
+  selected in the .config file.  They were used with #ifdefs, ala:
+
+    #ifdef CONFIG_SYMBOL
+      if (other_test) {
+        do_code();
+      }
+    #endif
+
+  In 1.1, we have new ENABLE_SYMBOLS which are always defined (as 0 or 1),
+  meaning you can still use them for preprocessor tests by replacing
+  "#ifdef CONFIG_SYMBOL" with "#if ENABLE_SYMBOL".  But more importantly, we
+  can use them as a true or false test in normal C code:
+
+    if (ENABLE_SYMBOL && other_test) {
+      do_code();
+    }
+
+  (Optimizing away if() statements that resolve to a constant value
+  is known as "dead code elimination", an optimization so old and simple that
+  Turbo Pascal for DOS did it twenty years ago.  Even modern mini-compilers
+  like the Tiny C Compiler (tcc) and the Small Device C Compiler (SDCC)
+  perform dead code elimination.)
+
+  Right now, busybox.h is #including both "config.h" (defining the
+  CONFIG_SYMBOLS) and "bb_config.h" (defining the ENABLE_SYMBOLS).  At some
+  point in the future, it would be nice to wean ourselves off of the
+  CONFIG versions.  (Among other things, some defective build environments
+  leak the Linux kernel's CONFIG_SYMBOLS into the system's standard #include
+  files.  We've experienced collisions before.)
+---
+FEATURE_CLEAN_UP
+  This is more an unresolved issue than a to-do item.  More thought is needed.
+
+  Normally we rely on exit() to free memory, close files, and unmap segments
+  for us.  This makes most calls to free(), close(), and unmap() optional in
+  busybox applets that don't intend to run for very long, and optional stuff
+  can be omitted to save size.
+
+  The idea was raised that we could simulate fork/exit with setjmp/longjmp
+  for _really_ brainless embedded systems, or speed up the standalone shell
+  by not forking.  Doing so would require a reliable FEATURE_CLEAN_UP.
+  Unfortunately, this isn't as easy as it sounds.
+
+  The problem is, lots of things exit(), sometimes unexpectedly (xmalloc())
+  and sometimes reliably (bb_perror_msg_and_die() or show_usage()).  This
+  jumps out of the normal flow control and bypasses any cleanup code we
+  put at the end of our applets.
+
+  It's possible to add hooks to libbb functions like xmalloc() and xopen()
+  to add their entries to a linked list, which could be traversed and
+  freed/closed automatically.  (This would need to be able to free just the
+  entries after a checkpoint to be usable for a forkless standalone shell.
+  You don't want to free the shell's own resources.)
+
+  Right now, FEATURE_CLEAN_UP is more or less a debugging aid, to make things
+  like valgrind happy.  It's also documentation of _what_ we're trusting
+  exit() to clean up for us.  But new infrastructure to auto-free stuff would
+  render the existing FEATURE_CLEAN_UP code redundant.
+
+  For right now, exit() handles it just fine.
+
+
+
+Minor stuff:
+  watchdog.c could autodetect the timer duration via:
+    if(!ioctl (fd, WDIOC_GETTIMEOUT, &tmo)) timer_duration = 1 + (tmo / 2);
+  Unfortunately, that needs linux/watchdog.h and that contains unfiltered
+  kernel types on some distros, which breaks the build.
+---
+  use bb_error_msg where appropriate: See
+  egrep "(printf.*\([[:space:]]*(stderr|2)|[^_]write.*\([[:space:]]*(stderr|2))"
+---
+  use bb_perror_msg where appropriate: See
+  egrep "[^_]perror"
+---
+  Remove superfluous fmt occurances: e.g.
+  fprintf(stderr, "%s: %s not found\n", "unalias", *argptr);
+  -> fprintf(stderr, "unalias: %s not found\n", *argptr);
+---
+  possible code duplication ingroup() and is_a_group_member()
+---
+  Move __get_hz() to a better place and (re)use it in route.c, ash.c, msh.c
+---
+  See grep -r strtod
+  Alot of duplication that wants cleanup.
+---
+
+
+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..42d1731
--- /dev/null
@@ -0,0 +1,837 @@
+#
+# Automatically generated make config: don't edit
+# Busybox version: 1.10.0.svn
+# Thu Mar 20 14:54:21 2008
+#
+CONFIG_HAVE_DOT_CONFIG=y
+
+#
+# Busybox Settings
+#
+
+#
+# General Configuration
+#
+CONFIG_NITPICK=y
+CONFIG_DESKTOP=y
+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_NOMMU=y
+# CONFIG_BUILD_LIBBUSYBOX is not set
+# CONFIG_FEATURE_INDIVIDUAL is not set
+# CONFIG_FEATURE_SHARED_BUSYBOX is not set
+CONFIG_LFS=y
+
+#
+# Debugging Options
+#
+# CONFIG_DEBUG 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
+
+#
+# Applets
+#
+
+#
+# Archival Utilities
+#
+CONFIG_AR=y
+CONFIG_FEATURE_AR_LONG_FILENAMES=y
+CONFIG_BUNZIP2=y
+CONFIG_BZIP2=y
+CONFIG_CPIO=y
+CONFIG_DPKG=y
+CONFIG_DPKG_DEB=y
+CONFIG_FEATURE_DPKG_DEB_EXTRACT_ONLY=y
+CONFIG_GUNZIP=y
+CONFIG_FEATURE_GUNZIP_UNCOMPRESS=y
+CONFIG_GZIP=y
+CONFIG_RPM2CPIO=y
+CONFIG_RPM=y
+CONFIG_FEATURE_RPM_BZ2=y
+CONFIG_TAR=y
+CONFIG_FEATURE_TAR_CREATE=y
+CONFIG_FEATURE_TAR_GZIP=y
+CONFIG_FEATURE_TAR_BZIP2=y
+CONFIG_FEATURE_TAR_LZMA=y
+CONFIG_FEATURE_TAR_COMPRESS=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
+
+#
+# Common options for cpio and tar
+#
+CONFIG_FEATURE_UNARCHIVE_TAPE=y
+
+#
+# Common options for dpkg and dpkg_deb
+#
+CONFIG_FEATURE_DEB_TAR_GZ=y
+CONFIG_FEATURE_DEB_TAR_BZ2=y
+CONFIG_FEATURE_DEB_TAR_LZMA=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_INODE=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_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_SETKEYCODES=y
+CONFIG_SETLOGCONS=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_MATH=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_DEBUG_INIT is not set
+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_SHADOW=y
+CONFIG_USE_BB_PWD_GRP=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_INSMOD=y
+CONFIG_FEATURE_INSMOD_VERSION_CHECKING=y
+CONFIG_FEATURE_INSMOD_KSYMOOPS_SYMBOLS=y
+CONFIG_FEATURE_INSMOD_LOADINKMEM=y
+CONFIG_FEATURE_INSMOD_LOAD_MAP=y
+CONFIG_FEATURE_INSMOD_LOAD_MAP_FULL=y
+CONFIG_RMMOD=y
+CONFIG_LSMOD=y
+CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT=y
+CONFIG_MODPROBE=y
+CONFIG_FEATURE_MODPROBE_MULTIPLE_OPTIONS=y
+CONFIG_FEATURE_MODPROBE_FANCY_ALIAS=y
+
+#
+# Options common to multiple modutils
+#
+CONFIG_FEATURE_CHECK_TAINTED_MODULE=y
+CONFIG_FEATURE_2_4_MODULES=y
+CONFIG_FEATURE_2_6_MODULES=y
+# CONFIG_FEATURE_QUERY_MODULE_INTERFACE is not set
+
+#
+# Linux System Utilities
+#
+CONFIG_DMESG=y
+CONFIG_FEATURE_DMESG_PRETTY=y
+CONFIG_FBSET=y
+CONFIG_FEATURE_FBSET_FANCY=y
+CONFIG_FEATURE_FBSET_READMODE=y
+CONFIG_FDFLUSH=y
+CONFIG_FDFORMAT=y
+CONFIG_FDISK=y
+CONFIG_FDISK_SUPPORT_LARGE_DISKS=y
+CONFIG_FEATURE_FDISK_WRITABLE=y
+CONFIG_FEATURE_AIX_LABEL=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_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_READPROFILE=y
+CONFIG_RTCWAKE=y
+CONFIG_SETARCH=y
+CONFIG_SWAPONOFF=y
+CONFIG_SWITCH_ROOT=y
+CONFIG_UMOUNT=y
+CONFIG_FEATURE_UMOUNT_ALL=y
+
+#
+# Common options for mount/umount
+#
+CONFIG_FEATURE_MOUNT_LOOP=y
+# CONFIG_FEATURE_MTAB_SUPPORT is not set
+
+#
+# Miscellaneous Utilities
+#
+CONFIG_ADJTIMEX=y
+CONFIG_BBCONFIG=y
+CONFIG_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_DEBUG_CROND_OPTION=y
+CONFIG_FEATURE_CROND_CALL_SENDMAIL=y
+CONFIG_CRONTAB=y
+CONFIG_DC=y
+# CONFIG_DEVFSD is not set
+# CONFIG_DEVFSD_MODLOAD is not set
+# CONFIG_DEVFSD_FG_NP is not set
+# CONFIG_DEVFSD_VERBOSE is not set
+# CONFIG_FEATURE_DEVFS is not set
+CONFIG_EJECT=y
+CONFIG_FEATURE_EJECT_SCSI=y
+CONFIG_LAST=y
+CONFIG_LESS=y
+CONFIG_FEATURE_LESS_MAXLINES=9999999
+CONFIG_FEATURE_LESS_BRACKETS=y
+CONFIG_FEATURE_LESS_FLAGS=y
+CONFIG_FEATURE_LESS_FLAGCS=y
+CONFIG_FEATURE_LESS_MARKS=y
+CONFIG_FEATURE_LESS_REGEXP=y
+CONFIG_HDPARM=y
+CONFIG_FEATURE_HDPARM_GET_IDENTITY=y
+CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF=y
+CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF=y
+CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET=y
+CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF=y
+CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA=y
+CONFIG_MAKEDEVS=y
+# CONFIG_FEATURE_MAKEDEVS_LEAF is not set
+CONFIG_FEATURE_MAKEDEVS_TABLE=y
+CONFIG_MICROCOM=y
+CONFIG_MOUNTPOINT=y
+CONFIG_MT=y
+CONFIG_RAIDAUTORUN=y
+CONFIG_READAHEAD=y
+CONFIG_RUNLEVEL=y
+CONFIG_RX=y
+CONFIG_SCRIPT=y
+CONFIG_STRINGS=y
+CONFIG_SETSID=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_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_RELOAD_CONFIG_SIGHUP=y
+CONFIG_FEATURE_HTTPD_SETUID=y
+CONFIG_FEATURE_HTTPD_BASIC_AUTH=y
+CONFIG_FEATURE_HTTPD_AUTH_MD5=y
+CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES=y
+CONFIG_FEATURE_HTTPD_CGI=y
+CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR=y
+CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV=y
+CONFIG_FEATURE_HTTPD_ENCODE_URL_STR=y
+CONFIG_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_NSLOOKUP=y
+CONFIG_PING=y
+CONFIG_PING6=y
+CONFIG_FEATURE_FANCY_PING=y
+CONFIG_PSCAN=y
+CONFIG_ROUTE=y
+CONFIG_SENDMAIL=y
+CONFIG_FETCHMAIL=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_DEBUG_TFTP=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_FEATURE_UDHCP_DEBUG=y
+CONFIG_FEATURE_RFC3397=y
+CONFIG_DHCPC_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
+
+#
+# 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_DECIMALS=y
+CONFIG_FEATURE_TOPMEM=y
+CONFIG_UPTIME=y
+CONFIG_WATCH=y
+
+#
+# Shells
+#
+# CONFIG_FEATURE_SH_IS_ASH is not set
+# CONFIG_FEATURE_SH_IS_HUSH is not set
+# CONFIG_FEATURE_SH_IS_MSH is not set
+CONFIG_FEATURE_SH_IS_NONE=y
+# CONFIG_ASH is not set
+# CONFIG_ASH_JOB_CONTROL is not set
+# CONFIG_ASH_READ_NCHARS is not set
+# CONFIG_ASH_READ_TIMEOUT is not set
+# CONFIG_ASH_ALIAS is not set
+# CONFIG_ASH_MATH_SUPPORT is not set
+# CONFIG_ASH_MATH_SUPPORT_64 is not set
+# CONFIG_ASH_GETOPTS is not set
+# CONFIG_ASH_BUILTIN_ECHO is not set
+# CONFIG_ASH_BUILTIN_TEST is not set
+# CONFIG_ASH_CMDCMD is not set
+# CONFIG_ASH_MAIL is not set
+# CONFIG_ASH_OPTIMIZE_FOR_SIZE is not set
+# CONFIG_ASH_RANDOM_SUPPORT is not set
+# CONFIG_ASH_EXPAND_PRMT is not set
+CONFIG_HUSH=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_LASH=y
+CONFIG_MSH=y
+
+#
+# Bourne Shell Options
+#
+CONFIG_FEATURE_SH_EXTRA_QUIET=y
+CONFIG_FEATURE_SH_STANDALONE=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
+
+#
+# Runit Utilities
+#
+CONFIG_RUNSV=y
+CONFIG_RUNSVDIR=y
+CONFIG_SV=y
+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
+
+#
+# Print Utilities
+#
+CONFIG_LPD=y
+CONFIG_LPR=y
+CONFIG_LPQ=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..6c3492b
--- /dev/null
@@ -0,0 +1,115 @@
+/* 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;
+
+       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 */");
+
+       puts("const char applet_names[] ALIGN1 = \"\"\n");
+       for (i = 0; i < NUM_APPLETS; i++) {
+               printf("\"%s\" \"\\0\"\n", applets[i].name);
+       }
+       puts(";");
+
+       puts("int (*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("};");
+#endif
+
+       return 0;
+}
diff --git a/applets/applets.c b/applets/applets.c
new file mode 100644 (file)
index 0000000..fbe7666
--- /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 ATTRIBUTE_UNUSED, 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..0c7a4b7
--- /dev/null
@@ -0,0 +1,26 @@
+/* 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)
+{
+       printf(APPLET_full_usage "\n");
+
+       exit(1);
+}
diff --git a/applets/install.sh b/applets/install.sh
new file mode 100755 (executable)
index 0000000..e94b2b9
--- /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 -a $i $prefix/$libdir/ || exit 1
+                       chmod 0644 $prefix/$libdir/$i || exit 1
+               fi
+       done
+fi
+
+if [ "$cleanup" = "1" ] && [ -e "$prefix/bin/busybox" ]; then
+       inode=`ls -i "$prefix/bin/busybox" | awk '{print $1}'`
+       sub_shell_it=`
+       cd "$prefix"
+       for d in usr/sbin usr/bin sbin bin; do
+               pd=$PWD
+               if [ -d "$d" ]; then
+                       cd $d
+                       ls -iL . | grep "^ *$inode" | awk '{print $2}' | env -i xargs rm -f
+               fi
+               cd "$pd"
+       done
+       `
+       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..4955839
--- /dev/null
@@ -0,0 +1,18 @@
+/* vi: set sw=4 ts=4: */
+#include <unistd.h>
+
+/* Just #include "autoconf.h" doesn't work for builds in separate
+ * object directory */
+#include "../include/autoconf.h"
+
+static const char usage_messages[] = ""
+#define MAKE_USAGE
+#include "usage.h"
+#include "applets.h"
+;
+
+int main(void)
+{
+       write(1, usage_messages, sizeof(usage_messages));
+       return 0;
+}
diff --git a/applets/usage_compressed b/applets/usage_compressed
new file mode 100755 (executable)
index 0000000..551b4b4
--- /dev/null
@@ -0,0 +1,27 @@
+#!/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 = {'
+
+# 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'
+
+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..60c3ed2
--- /dev/null
@@ -0,0 +1,343 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Archival Utilities"
+
+config AR
+       bool "ar"
+       default n
+       help
+         ar is an archival utility program used to create, modify, and
+         extract contents from archives.  An archive is a single file holding
+         a collection of other files in a structure that makes it possible to
+         retrieve the original individual files (called archive members).
+         The original files' contents, mode (permissions), timestamp, owner,
+         and group are preserved in the archive, and can be restored on
+         extraction.
+
+         The stored filename is limited to 15 characters. (for more information
+         see long filename support).
+         ar has 60 bytes of overheads for every stored file.
+
+         This implementation of ar can extract archives, it cannot create or
+         modify them.
+         On an x86 system, the ar applet adds about 1K.
+
+         Unless you have a specific application which requires ar, you should
+         probably say N here.
+
+config FEATURE_AR_LONG_FILENAMES
+       bool "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 DPKG
+       bool "dpkg"
+       default n
+       help
+         dpkg is a medium-level tool to install, build, remove and manage Debian packages.
+
+         This implementation of dpkg has a number of limitations, you should use the
+         official dpkg if possible.
+
+config DPKG_DEB
+       bool "dpkg_deb"
+       default n
+       help
+         dpkg-deb packs, unpacks and provides information about Debian archives.
+
+         This implementation of dpkg-deb cannot pack archives.
+
+         Unless you have a specific application which requires dpkg-deb, you should
+         probably say N here.
+
+config FEATURE_DPKG_DEB_EXTRACT_ONLY
+       bool "Extract only (-x)"
+       default n
+       depends on DPKG_DEB
+       help
+         This reduces dpkg-deb to the equivalent of "ar -p <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 FEATURE_GUNZIP_UNCOMPRESS
+       bool "Uncompress support"
+       default n
+       depends on GUNZIP
+       help
+         Enable if you want gunzip to have the ability to decompress
+         archives created by the program compress (not much
+         used anymore).
+
+config GZIP
+       bool "gzip"
+       default n
+       help
+         gzip is used to compress files.
+         It's probably the most widely used UNIX compression program.
+
+config RPM2CPIO
+       bool "rpm2cpio"
+       default n
+       help
+         Converts an RPM file into a CPIO archive.
+
+config RPM
+       bool "rpm"
+       default n
+       help
+         Mini RPM applet - queries and extracts RPM packages.
+
+config FEATURE_RPM_BZ2
+       bool "Enable handling of rpms with bzip2-compressed data inside"
+       default n
+       depends on RPM
+       help
+         Enable handling of rpms with bzip2-compressed data inside.
+
+config TAR
+       bool "tar"
+       default n
+       help
+         tar is an archiving program. It's commonly used with gzip to
+         create compressed archives. It's probably the most widely used
+         UNIX archive program.
+
+config FEATURE_TAR_CREATE
+       bool "Enable archive creation"
+       default y
+       depends on TAR
+       help
+         If you enable this option you'll be able to create
+         tar archives using the `-c' option.
+
+config FEATURE_TAR_GZIP
+       bool "Enable -z option"
+       default y
+       depends on TAR
+       help
+         If you enable this option tar will be able to call gzip,
+         when creating or extracting tar gziped archives.
+
+config FEATURE_TAR_BZIP2
+       bool "Enable -j option to handle .tar.bz2 files"
+       default n
+       depends on TAR
+       help
+         If you enable this option you'll be able to extract
+         archives compressed with bzip2.
+
+config FEATURE_TAR_LZMA
+       bool "Enable -a option to handle .tar.lzma files"
+       default n
+       depends on TAR
+       help
+         If you enable this option you'll be able to extract
+         archives compressed with lzma.
+
+config FEATURE_TAR_COMPRESS
+       bool "Enable -Z option"
+       default n
+       depends on TAR
+       help
+         If you enable this option tar will be able to call uncompress,
+         when extracting .tar.Z archives.
+
+config FEATURE_TAR_AUTODETECT
+       bool "Let tar autodetect gz/bz2 compresses tarballs"
+       default n
+       depends on FEATURE_TAR_GZIP || FEATURE_TAR_BZIP2
+       help
+         With this option tar can automatically detect gzip/bzip2 compressed
+         tarballs. Currently it works only on seekable streams.
+
+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.
+
+config UNCOMPRESS
+       bool "uncompress"
+       default n
+       help
+         uncompress is used to decompress archives created by compress.
+         Not much used anymore, replaced by gzip/gunzip.
+
+config UNLZMA
+       bool "unlzma"
+       default n
+       help
+         unlzma is a compression utility using the Lempel-Ziv-Markov chain
+         compression algorithm, and range coding.  Compression
+         is generally considerably better than that achieved by the bzip2
+         compressors.
+
+         The BusyBox unlzma applet is limited to de-compression only.
+         On an x86 system, this applet adds about 4K.
+
+         Unless you have a specific application which requires unlzma, you
+         should probably say N here.
+
+config FEATURE_LZMA_FAST
+       bool "Optimze unlzma for speed"
+       default n
+       depends on UNLZMA
+       help
+         This option reduces decompression time by about 33% at the cost of
+         a 2K bigger binary.
+
+config UNZIP
+       bool "unzip"
+       default n
+       help
+         unzip will list or extract files from a ZIP archive,
+         commonly found on DOS/WIN systems. The default behavior
+         (with no options) is to extract the archive into the
+         current directory. Use the `-d' option to extract to a
+         directory of your choice.
+
+comment "Common options for cpio and tar"
+       depends on CPIO || TAR
+
+config FEATURE_UNARCHIVE_TAPE
+       bool "Enable tape drive support"
+       default n
+       depends on CPIO || TAR
+       help
+         I don't think this is needed anymore.
+
+comment "Common options for dpkg and dpkg_deb"
+       depends on DPKG || DPKG_DEB
+
+config FEATURE_DEB_TAR_GZ
+       bool "gzip debian packages (normal)"
+       default y if DPKG || DPKG_DEB
+       depends on DPKG || DPKG_DEB
+       help
+         This is the default compression method inside the debian ar file.
+
+         If you want compatibility with standard .deb's you should say yes here.
+
+config FEATURE_DEB_TAR_BZ2
+       bool "bzip2 debian packages"
+       default n
+       depends on DPKG || DPKG_DEB
+       help
+         This allows dpkg and dpkg-deb to extract deb's that are compressed internally
+         with bzip2 instead of gzip.
+
+         You only want this if you are creating your own custom debian packages that
+         use an internal control.tar.bz2 or data.tar.bz2.
+
+config FEATURE_DEB_TAR_LZMA
+       bool "lzma debian packages"
+       default n
+       depends on DPKG || DPKG_DEB
+       help
+         This allows dpkg and dpkg-deb to extract deb's that are compressed
+         internally with lzma instead of gzip.
+
+         You only want this if you are creating your own custom debian
+         packages that use an internal control.tar.lzma or data.tar.lzma.
+
+endmenu
diff --git a/archival/Kbuild b/archival/Kbuild
new file mode 100644 (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..0a95e5c
--- /dev/null
@@ -0,0 +1,95 @@
+/* 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 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;
+       char magic[8];
+
+       archive_handle = init_handle();
+
+       /* Prepend '-' to the first argument if required */
+       opt_complementary = "--:p:t:x:-1:p--tx:t--px:x--pt";
+       opt = getopt32(argv, "ptxovcr");
+
+       if (opt & AR_CTX_PRINT) {
+               archive_handle->action_data = data_extract_to_stdout;
+       }
+       if (opt & AR_CTX_LIST) {
+               archive_handle->action_header = header_list;
+       }
+       if (opt & AR_CTX_EXTRACT) {
+               archive_handle->action_data = data_extract_all;
+       }
+       if (opt & AR_OPT_PRESERVE_DATE) {
+               archive_handle->flags |= ARCHIVE_PRESERVE_DATE;
+       }
+       if (opt & AR_OPT_VERBOSE) {
+               archive_handle->action_header = header_verbose_list_ar;
+       }
+       if (opt & AR_OPT_CREATE) {
+               bb_error_msg_and_die(msg_unsupported_err, "creation");
+       }
+       if (opt & AR_OPT_INSERT) {
+               bb_error_msg_and_die(msg_unsupported_err, "insertion");
+       }
+
+       archive_handle->src_fd = xopen(argv[optind++], O_RDONLY);
+
+       while (optind < argc) {
+               archive_handle->filter = filter_accept_list;
+               llist_add_to(&(archive_handle->accept), argv[optind++]);
+       }
+
+       xread(archive_handle->src_fd, magic, 7);
+       if (strncmp(magic, "!<arch>", 7) != 0) {
+               bb_error_msg_and_die("invalid ar magic");
+       }
+       archive_handle->offset += 7;
+
+       while (get_header_ar(archive_handle) == EXIT_SUCCESS)
+               continue;
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/bbunzip.c b/archival/bbunzip.c
new file mode 100644 (file)
index 0000000..327b3cf
--- /dev/null
@@ -0,0 +1,348 @@
+/* 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 bbunpack(char **argv,
+       char* (*make_new_name)(char *filename),
+       USE_DESKTOP(long long) int (*unpacker)(void)
+)
+{
+       struct stat stat_buf;
+       USE_DESKTOP(long long) int status;
+       char *filename, *new_name;
+       smallint exitcode = 0;
+
+       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;
+                       }
+                       /* 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");
+               }
+
+               status = unpacker();
+               if (status < 0)
+                       exitcode = 1;
+
+               if (filename) {
+                       char *del = new_name;
+                       if (status >= 0) {
+                               /* TODO: restore user/group/times here? */
+                               /* 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(void)
+{
+       return unpack_bz2_stream(STDIN_FILENO, STDOUT_FILENO);
+}
+
+int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int bunzip2_main(int argc ATTRIBUTE_UNUSED, 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_GUNZIP_UNCOMPRESS
+        || strcmp(extension, "Z") == 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(void)
+{
+       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_GUNZIP_UNCOMPRESS && magic2 == 0x9d) {
+                       status = uncompress(STDIN_FILENO, STDOUT_FILENO);
+               } else if (magic2 == 0x8b) {
+                       status = unpack_gz_stream(STDIN_FILENO, STDOUT_FILENO);
+               } 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;
+}
+
+int gunzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int gunzip_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       getopt32(argv, "cfvdt");
+       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(void)
+{
+       return unpack_lzma_stream(STDIN_FILENO, STDOUT_FILENO);
+}
+
+int unlzma_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int unlzma_main(int argc ATTRIBUTE_UNUSED, 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(void)
+{
+       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 = uncompress(STDIN_FILENO, STDOUT_FILENO);
+       }
+       return status;
+}
+
+int uncompress_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int uncompress_main(int argc ATTRIBUTE_UNUSED, 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..3015342
--- /dev/null
@@ -0,0 +1,90 @@
+This file is an abridged version of README from bizp2 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..48676a3
--- /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) ATTRIBUTE_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] __attribute__(( 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..eb570c4
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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"
+
+#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(void)
+{
+       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 ATTRIBUTE_UNUSED, 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("d") "123456789qzs" );
+#if ENABLE_BUNZIP2 /* bunzip2_main may not be visible... */
+       if (opt & 0x8) // -d
+               return bunzip2_main(argc, argv);
+       opt >>= 4;
+#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..59ae60c
--- /dev/null
@@ -0,0 +1,83 @@
+/* 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"
+
+#define CPIO_OPT_EXTRACT                0x01
+#define CPIO_OPT_TEST                   0x02
+#define CPIO_OPT_UNCONDITIONAL          0x04
+#define CPIO_OPT_VERBOSE                0x08
+#define CPIO_OPT_FILE                   0x10
+#define CPIO_OPT_CREATE_LEADING_DIR     0x20
+#define CPIO_OPT_PRESERVE_MTIME         0x40
+
+int cpio_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cpio_main(int argc, char **argv)
+{
+       archive_handle_t *archive_handle;
+       char *cpio_filename = NULL;
+       unsigned opt;
+
+       /* Initialise */
+       archive_handle = init_handle();
+       archive_handle->src_fd = STDIN_FILENO;
+       archive_handle->seek = seek_by_read;
+       archive_handle->flags = ARCHIVE_EXTRACT_NEWER | ARCHIVE_PRESERVE_DATE;
+
+       opt = getopt32(argv, "ituvF:dm", &cpio_filename);
+
+       /* One of either extract or test options must be given */
+       if ((opt & (CPIO_OPT_TEST | CPIO_OPT_EXTRACT)) == 0) {
+               bb_show_usage();
+       }
+
+       if (opt & CPIO_OPT_TEST) {
+               /* if both extract and test options are given, ignore extract option */
+               if (opt & CPIO_OPT_EXTRACT) {
+                       opt &= ~CPIO_OPT_EXTRACT;
+               }
+               archive_handle->action_header = header_list;
+       }
+       if (opt & CPIO_OPT_EXTRACT) {
+               archive_handle->action_data = data_extract_all;
+       }
+       if (opt & CPIO_OPT_UNCONDITIONAL) {
+               archive_handle->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
+               archive_handle->flags &= ~ARCHIVE_EXTRACT_NEWER;
+       }
+       if (opt & CPIO_OPT_VERBOSE) {
+               if (archive_handle->action_header == header_list) {
+                       archive_handle->action_header = header_verbose_list;
+               } else {
+                       archive_handle->action_header = header_list;
+               }
+       }
+       if (cpio_filename) { /* CPIO_OPT_FILE */
+               archive_handle->src_fd = xopen(cpio_filename, O_RDONLY);
+               archive_handle->seek = seek_by_jump;
+       }
+       if (opt & CPIO_OPT_CREATE_LEADING_DIR) {
+               archive_handle->flags |= ARCHIVE_CREATE_LEADING_DIRS;
+       }
+
+       while (optind < argc) {
+               archive_handle->filter = filter_accept_list;
+               llist_add_to(&(archive_handle->accept), argv[optind]);
+               optind++;
+       }
+
+       while (get_header_cpio(archive_handle) == EXIT_SUCCESS);
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/dpkg.c b/archival/dpkg.c
new file mode 100644 (file)
index 0000000..7693342
--- /dev/null
@@ -0,0 +1,1752 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  mini dpkg implementation for busybox.
+ *  this is not meant as a replacement for dpkg
+ *
+ *  written by glenn mcgrath with the help of others
+ *  copyright (c) 2001 by glenn mcgrath
+ *
+ *  started life as a busybox implementation of udpkg
+ *
+ * licensed under gplv2 or later, see file license in this tarball for details.
+ */
+
+/*
+ * known difference between busybox dpkg and the official dpkg that i don't
+ * consider important, its worth keeping a note of differences anyway, just to
+ * make it easier to maintain.
+ *  - the first value for the confflile: field isnt placed on a new line.
+ *  - when installing a package the status: field is placed at the end of the
+ *      section, rather than just after the package: field.
+ *
+ * bugs that need to be fixed
+ *  - (unknown, please let me know when you find any)
+ *
+ */
+
+#include "libbb.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;
+
+/* Were statically declared here, but such a big bss is nommu-unfriendly */
+static char **name_hashtable;             /* [NAME_HASH_PRIME + 1] */
+static common_node_t **package_hashtable; /* [PACKAGE_HASH_PRIME + 1] */
+static status_node_t **status_hashtable;  /* [STATUS_HASH_PRIME + 1] */
+
+/* Even numbers are for 'extras', like ored dependencies or null */
+enum edge_type_e {
+       EDGE_NULL = 0,
+       EDGE_PRE_DEPENDS = 1,
+       EDGE_OR_PRE_DEPENDS = 2,
+       EDGE_DEPENDS = 3,
+       EDGE_OR_DEPENDS = 4,
+       EDGE_REPLACES = 5,
+       EDGE_PROVIDES = 7,
+       EDGE_CONFLICTS = 9,
+       EDGE_SUGGESTS = 11,
+       EDGE_RECOMMENDS = 13,
+       EDGE_ENHANCES = 15
+};
+enum operator_e {
+       VER_NULL = 0,
+       VER_EQUAL = 1,
+       VER_LESS = 2,
+       VER_LESS_EQUAL = 3,
+       VER_MORE = 4,
+       VER_MORE_EQUAL = 5,
+       VER_ANY = 6
+};
+
+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 = 0;
+       unsigned probe_decrement = 0;
+
+       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 = 0;
+       unsigned probe_decrement = 0;
+
+       make_hash(key, &probe_address, &probe_decrement, STATUS_HASH_PRIME);
+       while (status_hashtable[probe_address] != NULL) {
+               if (strcmp(key, name_hashtable[package_hashtable[status_hashtable[probe_address]->package]->name]) == 0) {
+                       break;
+               }
+               probe_address -= probe_decrement;
+               if ((int)probe_address < 0) {
+                       probe_address += STATUS_HASH_PRIME;
+               }
+       }
+       return probe_address;
+}
+
+/* Need to rethink version comparison, maybe the official dpkg has something i can use ? */
+static int version_compare_part(const char *version1, const char *version2)
+{
+       int upstream_len1 = 0;
+       int upstream_len2 = 0;
+       char *name1_char;
+       char *name2_char;
+       int len1 = 0;
+       int len2 = 0;
+       int tmp_int;
+       int ver_num1;
+       int ver_num2;
+
+       if (version1 == NULL) {
+               version1 = xstrdup("");
+       }
+       if (version2 == NULL) {
+               version2 = xstrdup("");
+       }
+       upstream_len1 = strlen(version1);
+       upstream_len2 = strlen(version2);
+
+       while ((len1 < upstream_len1) || (len2 < upstream_len2)) {
+               /* Compare non-digit section */
+               tmp_int = strcspn(&version1[len1], "0123456789");
+               name1_char = xstrndup(&version1[len1], tmp_int);
+               len1 += tmp_int;
+               tmp_int = strcspn(&version2[len2], "0123456789");
+               name2_char = xstrndup(&version2[len2], tmp_int);
+               len2 += tmp_int;
+               tmp_int = strcmp(name1_char, name2_char);
+               free(name1_char);
+               free(name2_char);
+               if (tmp_int != 0) {
+                       return tmp_int;
+               }
+
+               /* Compare digits */
+               tmp_int = strspn(&version1[len1], "0123456789");
+               name1_char = xstrndup(&version1[len1], tmp_int);
+               len1 += tmp_int;
+               tmp_int = strspn(&version2[len2], "0123456789");
+               name2_char = xstrndup(&version2[len2], tmp_int);
+               len2 += tmp_int;
+               ver_num1 = atoi(name1_char);
+               ver_num2 = atoi(name2_char);
+               free(name1_char);
+               free(name2_char);
+               if (ver_num1 < ver_num2) {
+                       return -1;
+               }
+               if (ver_num1 > ver_num2) {
+                       return 1;
+               }
+       }
+       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];
+
+       char epoch1, epoch2;
+       char *deb_ver1, *deb_ver2;
+       char *ver1_ptr, *ver2_ptr;
+       char *upstream_ver1;
+       char *upstream_ver2;
+       int result;
+
+       /* Compare epoch */
+       if (ch_ver1[1] == ':') {
+               epoch1 = ch_ver1[0];
+               ver1_ptr = strchr(ch_ver1, ':') + 1;
+       } else {
+               epoch1 = '0';
+               ver1_ptr = ch_ver1;
+       }
+       if (ch_ver2[1] == ':') {
+               epoch2 = ch_ver2[0];
+               ver2_ptr = strchr(ch_ver2, ':') + 1;
+       } else {
+               epoch2 = '0';
+               ver2_ptr = ch_ver2;
+       }
+       if (epoch1 < epoch2) {
+               return -1;
+       }
+       else if (epoch1 > epoch2) {
+               return 1;
+       }
+
+       /* Compare upstream version */
+       upstream_ver1 = xstrdup(ver1_ptr);
+       upstream_ver2 = xstrdup(ver2_ptr);
+
+       /* Chop off debian version, and store for later use */
+       deb_ver1 = strrchr(upstream_ver1, '-');
+       deb_ver2 = strrchr(upstream_ver2, '-');
+       if (deb_ver1) {
+               deb_ver1[0] = '\0';
+               deb_ver1++;
+       }
+       if (deb_ver2) {
+               deb_ver2[0] = '\0';
+               deb_ver2++;
+       }
+       result = version_compare_part(upstream_ver1, upstream_ver2);
+       if (!result)
+               /* Compare debian versions */
+               result = version_compare_part(deb_ver1, deb_ver2);
+
+       free(upstream_ver1);
+       free(upstream_ver2);
+       return result;
+}
+
+static int test_version(const unsigned 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 = 0;
+       unsigned probe_decrement = 0;
+
+       make_hash(name_hashtable[name], &probe_address, &probe_decrement, PACKAGE_HASH_PRIME);
+       while (package_hashtable[probe_address] != NULL) {
+               if (package_hashtable[probe_address]->name == name) {
+                       if (operator == VER_ANY) {
+                               return probe_address;
+                       }
+                       if (test_version(package_hashtable[probe_address]->version, version, operator)) {
+                               return probe_address;
+                       }
+               }
+               probe_address -= probe_decrement;
+               if ((int)probe_address < 0) {
+                       probe_address += PACKAGE_HASH_PRIME;
+               }
+       }
+       return probe_address;
+}
+
+/*
+ * This function searches through the entire package_hashtable looking
+ * for a package which provides "needle". It returns the index into
+ * the package_hashtable for the providing package.
+ *
+ * needle is the index into name_hashtable of the package we are
+ * looking for.
+ *
+ * start_at is the index in the package_hashtable to start looking
+ * at. If start_at is -1 then start at the beginning. This is to allow
+ * for repeated searches since more than one package might provide
+ * needle.
+ *
+ * FIXME: I don't think this is very efficient, but I thought I'd keep
+ * it simple for now until it proves to be a problem.
+ */
+static int search_for_provides(int needle, int start_at)
+{
+       int i, j;
+       common_node_t *p;
+       for (i = start_at + 1; i < PACKAGE_HASH_PRIME; i++) {
+               p = package_hashtable[i];
+               if (p == NULL)
+                       continue;
+               for (j = 0; j < p->num_of_edges; j++)
+                       if (p->edge[j]->type == EDGE_PROVIDES && p->edge[j]->name == needle)
+                               return i;
+       }
+       return -1;
+}
+
+/*
+ * Add an edge to a node
+ */
+static void add_edge_to_node(common_node_t *node, edge_t *edge)
+{
+       node->num_of_edges++;
+       node->edge = xrealloc(node->edge, sizeof(edge_t) * (node->num_of_edges + 1));
+       node->edge[node->num_of_edges - 1] = edge;
+}
+
+/*
+ * Create one new node and one new edge for every dependency.
+ *
+ * Dependencies which contain multiple alternatives are represented as
+ * an EDGE_OR_PRE_DEPENDS or EDGE_OR_DEPENDS node, followed by a
+ * number of EDGE_PRE_DEPENDS or EDGE_DEPENDS nodes. The name field of
+ * the OR edge contains the full dependency string while the version
+ * field contains the number of EDGE nodes which follow as part of
+ * this alternative.
+ */
+static void add_split_dependencies(common_node_t *parent_node, const char *whole_line, unsigned 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 = xmalloc(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(filename, "r");
+       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("/var/lib/dpkg/status", "r");
+       FILE *new_status_file = xfopen("/var/lib/dpkg/status.udeb", "w");
+       char *package_name;
+       char *status_from_file;
+       char *control_buffer = NULL;
+       char *tmp_string;
+       int status_num;
+       int field_start = 0;
+       int write_flag;
+       int i = 0;
+
+       /* Update previously known packages */
+       while ((control_buffer = xmalloc_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(conflicts, sizeof(int) * (conflicts_num + 1));
+               conflicts[conflicts_num] = package_num;
+               conflicts_num++;
+               /* add provides to conflicts list */
+               for (j = 0; j < package_hashtable[package_num]->num_of_edges; j++) {
+                       if (package_hashtable[package_num]->edge[j]->type == EDGE_PROVIDES) {
+                               const int conflicts_package_num = search_package_hashtable(
+                                       package_hashtable[package_num]->edge[j]->name,
+                                       package_hashtable[package_num]->edge[j]->version,
+                                       package_hashtable[package_num]->edge[j]->operator);
+                               if (package_hashtable[conflicts_package_num] == NULL) {
+                                       /* create a new package */
+                                       common_node_t *new_node = xzalloc(sizeof(common_node_t));
+                                       new_node->name = package_hashtable[package_num]->edge[j]->name;
+                                       new_node->version = package_hashtable[package_num]->edge[j]->version;
+                                       package_hashtable[conflicts_package_num] = new_node;
+                               }
+                               conflicts = xrealloc(conflicts, sizeof(int) * (conflicts_num + 1));
+                               conflicts[conflicts_num] = conflicts_package_num;
+                               conflicts_num++;
+                       }
+               }
+               i++;
+       }
+
+       /* Check conflicts */
+       i = 0;
+       while (deb_file[i] != NULL) {
+               const common_node_t *package_node = package_hashtable[deb_file[i]->package];
+               int status_num = 0;
+               status_num = search_status_hashtable(name_hashtable[package_node->name]);
+
+               if (get_status(status_num, 3) == search_name_hashtable("installed")) {
+                       i++;
+                       continue;
+               }
+
+               for (j = 0; j < package_node->num_of_edges; j++) {
+                       const edge_t *package_edge = package_node->edge[j];
+
+                       if (package_edge->type == EDGE_CONFLICTS) {
+                               const unsigned 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 = NULL;
+       char *line = NULL;
+       int count = 0;
+
+       /* don't use [xw]fopen here, handle error ourself */
+       list_stream = fopen(filename, "r");
+       if (list_stream == NULL) {
+               return NULL;
+       }
+
+       while ((line = xmalloc_getline(list_stream)) != NULL) {
+               file_list = xrealloc(file_list, sizeof(char *) * (count + 2));
+               file_list[count] = line;
+               count++;
+       }
+       fclose(list_stream);
+
+       if (count == 0) {
+               return NULL;
+       }
+       file_list[count] = NULL;
+       return file_list;
+}
+
+/* maybe i should try and hook this into remove_file.c somehow */
+static int remove_file_array(char **remove_names, char **exclude_names)
+{
+       struct stat path_stat;
+       int 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 int run_package_script(const char *package_name, const char *script_type)
+{
+       struct stat path_stat;
+       char *script_path;
+       int result;
+
+       script_path = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, script_type);
+
+       /* If the file doesnt exist is isnt a fatal */
+       result = lstat(script_path, &path_stat) < 0 ? EXIT_SUCCESS : system(script_path);
+       free(script_path);
+       return result;
+}
+
+static const char *const all_control_files[] = {
+       "preinst", "postinst", "prerm", "postrm",
+       "list", "md5sums", "shlibs", "conffiles",
+       "config", "templates", NULL
+};
+
+static char **all_control_list(const char *package_name)
+{
+       unsigned i = 0;
+       char **remove_files;
+
+       /* Create a list of all /var/lib/dpkg/info/<package> files */
+       remove_files = xzalloc(sizeof(all_control_files));
+       while (all_control_files[i]) {
+               remove_files[i] = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, all_control_files[i]);
+               i++;
+       }
+
+       return remove_files;
+}
+
+static void free_array(char **array)
+{
+       if (array) {
+               unsigned i = 0;
+               while (array[i]) {
+                       free(array[i]);
+                       i++;
+               }
+               free(array);
+       }
+}
+
+/* This function lists information on the installed packages. It loops through
+ * the status_hashtable to retrieve the info. This results in smaller code than
+ * scanning the status file. The resulting list, however, is unsorted.
+ */
+static void list_packages(void)
+{
+       int i;
+
+       puts("    Name           Version");
+       puts("+++-==============-==============");
+
+       /* go through status hash, dereference package hash and finally strings */
+       for (i = 0; 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];
+
+                       /* 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 */
+       if (run_package_script(package_name, "prerm") != 0) {
+               bb_error_msg_and_die("script failed, prerm failure");
+       }
+
+       /* Create a list of files to remove, and a separate list of those to keep */
+       sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name);
+       remove_files = create_list(list_name);
+
+       sprintf(conffile_name, "/var/lib/dpkg/info/%s.conffiles", package_name);
+       exclude_files = create_list(conffile_name);
+
+       /* Some directories can't be removed straight away, so do multiple passes */
+       while (remove_file_array(remove_files, exclude_files)) /*repeat */;
+       free_array(exclude_files);
+       free_array(remove_files);
+
+       /* Create a list of files in /var/lib/dpkg/info/<package>.* to keep  */
+       exclude_files = xzalloc(sizeof(char*) * 3);
+       exclude_files[0] = xstrdup(conffile_name);
+       exclude_files[1] = xasprintf("/var/lib/dpkg/info/%s.postrm", package_name);
+
+       /* Create a list of all /var/lib/dpkg/info/<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>.conffile to <package>.list */
+       xrename(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 */
+       if (run_package_script(package_name, "prerm") != 0) {
+               bb_error_msg_and_die("script failed, prerm failure");
+       }
+
+       /* Create a list of files to remove */
+       sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name);
+       remove_files = create_list(list_name);
+
+       exclude_files = xzalloc(sizeof(char*));
+
+       /* Some directories cant be removed straight away, so do multiple passes */
+       while (remove_file_array(remove_files, exclude_files)) /* repeat */;
+       free_array(remove_files);
+
+       /* Create a list of all /var/lib/dpkg/info/<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 */
+       if (run_package_script(package_name, "postrm") != 0) {
+               bb_error_msg_and_die("postrm failure.. set status to what?");
+       }
+
+       /* Change package status */
+       set_status(status_num, "not-installed", 3);
+}
+
+static archive_handle_t *init_archive_deb_ar(const char *filename)
+{
+       archive_handle_t *ar_handle;
+
+       /* Setup an ar archive handle that refers to the gzip sub archive */
+       ar_handle = init_handle();
+       ar_handle->filter = filter_accept_list_reassign;
+       ar_handle->src_fd = xopen(filename, O_RDONLY);
+
+       return ar_handle;
+}
+
+static void init_archive_deb_control(archive_handle_t *ar_handle)
+{
+       archive_handle_t *tar_handle;
+
+       /* Setup the tar archive handle */
+       tar_handle = init_handle();
+       tar_handle->src_fd = ar_handle->src_fd;
+
+       /* We don't care about data.tar.* or debian-binary, just control.tar.* */
+#if ENABLE_FEATURE_DEB_TAR_GZ
+       llist_add_to(&(ar_handle->accept), (char*)"control.tar.gz");
+#endif
+#if ENABLE_FEATURE_DEB_TAR_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_DEB_TAR_GZ
+       llist_add_to(&(ar_handle->accept), (char*)"data.tar.gz");
+#endif
+#if ENABLE_FEATURE_DEB_TAR_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 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 = NULL;
+       int i = 0;
+
+       /* If existing version, remove it first */
+       if (strcmp(name_hashtable[get_status(status_num, 3)], "installed") == 0) {
+               /* Package is already installed, remove old version first */
+               printf("Preparing to replace %s %s (using %s)...\n", package_name,
+                       name_hashtable[package_hashtable[status_package_num]->version],
+                       deb_file->filename);
+               remove_package(status_package_num, 0);
+       } else {
+               printf("Unpacking %s (from %s)...\n", package_name, deb_file->filename);
+       }
+
+       /* Extract control.tar.gz to /var/lib/dpkg/info/<package>.filename */
+       info_prefix = xasprintf("/var/lib/dpkg/info/%s.", package_name);
+       archive_handle = init_archive_deb_ar(deb_file->filename);
+       init_archive_deb_control(archive_handle);
+
+       while (all_control_files[i]) {
+               char *c = xasprintf("./%s", all_control_files[i]);
+               llist_add_to(&accept_list, c);
+               i++;
+       }
+       archive_handle->sub_archive->accept = accept_list;
+       archive_handle->sub_archive->filter = filter_accept_list;
+       archive_handle->sub_archive->action_data = data_extract_all_prefix;
+       archive_handle->sub_archive->buffer = info_prefix;
+       archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
+       unpack_ar_archive(archive_handle);
+
+       /* Run the preinst prior to extracting */
+       if (run_package_script(package_name, "preinst") != 0) {
+               /* when preinst returns exit code != 0 then quit installation process */
+               bb_error_msg_and_die("subprocess pre-installation script returned error");
+       }
+
+       /* Extract data.tar.gz to the root directory */
+       archive_handle = init_archive_deb_ar(deb_file->filename);
+       init_archive_deb_data(archive_handle);
+       archive_handle->sub_archive->action_data = data_extract_all_prefix;
+       archive_handle->sub_archive->buffer = (char*)"/"; /* huh? */
+       archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL;
+       unpack_ar_archive(archive_handle);
+
+       /* Create the list file */
+       list_filename = xasprintf("/var/lib/dpkg/info/%s.list", package_name);
+       out_stream = xfopen(list_filename, "w");
+       while (archive_handle->sub_archive->passed) {
+               /* the leading . has been stripped by data_extract_all_prefix already */
+               fputs(archive_handle->sub_archive->passed->data, out_stream);
+               fputc('\n', out_stream);
+               archive_handle->sub_archive->passed = archive_handle->sub_archive->passed->link;
+       }
+       fclose(out_stream);
+
+       /* change status */
+       set_status(status_num, "install", 1);
+       set_status(status_num, "unpacked", 3);
+
+       free(info_prefix);
+       free(list_filename);
+}
+
+static void configure_package(deb_file_t *deb_file)
+{
+       const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name];
+       const char *package_version = name_hashtable[package_hashtable[deb_file->package]->version];
+       const int status_num = search_status_hashtable(package_name);
+
+       printf("Setting up %s (%s)...\n", package_name, package_version);
+
+       /* Run the postinst script */
+       if (run_package_script(package_name, "postinst") != 0) {
+               /* TODO: handle failure gracefully */
+               bb_error_msg_and_die("postinst failure.. set status to what?");
+       }
+       /* Change status to reflect success */
+       set_status(status_num, "install", 1);
+       set_status(status_num, "installed", 3);
+}
+
+int dpkg_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dpkg_main(int argc, 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,
+       };
+
+       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)
+       argc -= optind;
+       argv += optind;
+       /* check for non-option argument if expected  */
+       if (!opt || (!argc && !(opt && OPT_list_installed)))
+               bb_show_usage();
+
+       name_hashtable = xzalloc(sizeof(name_hashtable[0]) * (NAME_HASH_PRIME + 1));
+       package_hashtable = xzalloc(sizeof(package_hashtable[0]) * (PACKAGE_HASH_PRIME + 1));
+       status_hashtable = xzalloc(sizeof(status_hashtable[0]) * (STATUS_HASH_PRIME + 1));
+
+/*     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();
+               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(deb_file, sizeof(deb_file[0]) * (deb_count + 2));
+               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..cbacc91
--- /dev/null
@@ -0,0 +1,97 @@
+/* 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 = NULL;
+       short argcount = 1;
+
+       /* Setup the tar archive handle */
+       tar_archive = init_handle();
+
+       /* Setup an ar archive handle that refers to the gzip sub archive */
+       ar_archive = init_handle();
+       ar_archive->sub_archive = tar_archive;
+       ar_archive->filter = filter_accept_list_reassign;
+
+#if ENABLE_FEATURE_DEB_TAR_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_DEB_TAR_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");
+
+       if (opt & DPKG_DEB_OPT_CONTENTS) {
+               tar_archive->action_header = header_verbose_list;
+       }
+       if (opt & DPKG_DEB_OPT_CONTROL) {
+               ar_archive->accept = control_tar_llist;
+               tar_archive->action_data = data_extract_all;
+               if (optind + 1 == argc) {
+                       extract_dir = "./DEBIAN";
+               } else {
+                       argcount++;
+               }
+       }
+       if (opt & DPKG_DEB_OPT_FIELD) {
+               /* Print the entire control file
+                * it should accept a second argument which specifies a
+                * specific field to print */
+               ar_archive->accept = control_tar_llist;
+               llist_add_to(&(tar_archive->accept), (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;
+               argcount = 2;
+       }
+
+       if ((optind + argcount) != argc) {
+               bb_show_usage();
+       }
+
+       tar_archive->src_fd = ar_archive->src_fd = xopen(argv[optind++], O_RDONLY);
+
+       /* Workout where to extract the files */
+       /* 2nd argument is a dir name */
+       if (argv[optind]) {
+               extract_dir = argv[optind];
+       }
+       if (extract_dir) {
+               mkdir(extract_dir, 0777); /* bb_make_directory(extract_dir, 0777, 0) */
+               xchdir(extract_dir);
+       }
+       unpack_ar_archive(ar_archive);
+
+       /* Cleanup */
+       close(ar_archive->src_fd);
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/gzip.c b/archival/gzip.c
new file mode 100644 (file)
index 0000000..a96d029
--- /dev/null
@@ -0,0 +1,2086 @@
+/* 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"
+
+
+/* ===========================================================================
+ */
+//#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);
+}
+
+/* ===========================================================================
+ * Clear input and output buffers
+ */
+static void clear_bufs(void)
+{
+       G1.outcnt = 0;
+#ifdef DEBUG
+       G1.insize = 0;
+#endif
+       G1.isize = 0;
+}
+
+
+/* ===========================================================================
+ * 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(void)
+{
+       struct stat s;
+
+       clear_bufs();
+       s.st_ctime = 0;
+       fstat(STDIN_FILENO, &s);
+       zip(s.st_ctime);
+       return 0;
+}
+
+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 ATTRIBUTE_UNUSED, char **argv)
+#endif
+{
+       unsigned opt;
+
+       /* Must match bbunzip's constants OPT_STDOUT, OPT_FORCE! */
+       opt = getopt32(argv, "cfv" USE_GUNZIP("d") "q123456789" );
+#if ENABLE_GUNZIP /* gunzip_main may not be visible... */
+       if (opt & 0x8) // -d
+               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));
+       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;
+
+       /* 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..1bc054a
--- /dev/null
@@ -0,0 +1,67 @@
+# 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 \
+\
+       archive_xread_all_eof.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_RPM)                       += open_transformer.o
+lib-$(CONFIG_FEATURE_TAR_BZIP2)         += open_transformer.o
+lib-$(CONFIG_FEATURE_TAR_LZMA)          += open_transformer.o
+lib-$(CONFIG_FEATURE_TAR_GZIP)          += open_transformer.o
+lib-$(CONFIG_FEATURE_TAR_COMPRESS)      += open_transformer.o
+lib-$(CONFIG_FEATURE_DEB_TAR_GZ)        += open_transformer.o
+lib-$(CONFIG_FEATURE_DEB_TAR_BZ2)       += open_transformer.o
+lib-$(CONFIG_FEATURE_DEB_TAR_LZMA)      += open_transformer.o
+
+lib-$(CONFIG_AR)                        += get_header_ar.o unpack_ar_archive.o
+lib-$(CONFIG_BUNZIP2)                   += decompress_bunzip2.o
+lib-$(CONFIG_UNLZMA)                    += decompress_unlzma.o
+lib-$(CONFIG_CPIO)                      += get_header_cpio.o
+lib-$(CONFIG_DPKG)                      += $(DPKG_FILES)
+lib-$(CONFIG_DPKG_DEB)                  += $(DPKG_FILES)
+lib-$(CONFIG_FEATURE_DEB_TAR_GZ)        += decompress_unzip.o get_header_tar_gz.o
+lib-$(CONFIG_FEATURE_DEB_TAR_BZ2)       += decompress_bunzip2.o get_header_tar_bz2.o
+lib-$(CONFIG_FEATURE_DEB_TAR_LZMA)      += decompress_unlzma.o get_header_tar_lzma.o
+lib-$(CONFIG_GUNZIP)                    += decompress_unzip.o
+lib-$(CONFIG_FEATURE_GUNZIP_UNCOMPRESS) += decompress_uncompress.o
+lib-$(CONFIG_RPM2CPIO)                  += decompress_unzip.o get_header_cpio.o
+lib-$(CONFIG_RPM)                       += decompress_unzip.o get_header_cpio.o
+lib-$(CONFIG_FEATURE_RPM_BZ2)           += decompress_bunzip2.o
+lib-$(CONFIG_TAR)                       += get_header_tar.o
+lib-$(CONFIG_FEATURE_TAR_BZIP2)         += decompress_bunzip2.o get_header_tar_bz2.o
+lib-$(CONFIG_FEATURE_TAR_LZMA)          += decompress_unlzma.o get_header_tar_lzma.o
+lib-$(CONFIG_FEATURE_TAR_GZIP)          += decompress_unzip.o get_header_tar_gz.o
+lib-$(CONFIG_FEATURE_TAR_COMPRESS)      += decompress_uncompress.o
+lib-$(CONFIG_UNCOMPRESS)                += decompress_uncompress.o
+lib-$(CONFIG_UNZIP)                     += decompress_unzip.o
+lib-$(CONFIG_FEATURE_COMPRESS_USAGE)    += decompress_bunzip2.o
diff --git a/archival/libunarchive/archive_xread_all_eof.c b/archival/libunarchive/archive_xread_all_eof.c
new file mode 100644 (file)
index 0000000..7e082ab
--- /dev/null
@@ -0,0 +1,20 @@
+/* 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"
+
+ssize_t archive_xread_all_eof(archive_handle_t *archive_handle,
+                       unsigned char *buf, size_t count)
+{
+       ssize_t size;
+
+       size = full_read(archive_handle->src_fd, buf, count);
+       if (size != 0 && size != count) {
+               bb_error_msg_and_die("short read: %u of %u",
+                               (unsigned)size, (unsigned)count);
+       }
+       return size;
+}
diff --git a/archival/libunarchive/data_align.c b/archival/libunarchive/data_align.c
new file mode 100644 (file)
index 0000000..d98dc57
--- /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 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..4df9c09
--- /dev/null
@@ -0,0 +1,146 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+void data_extract_all(archive_handle_t *archive_handle)
+{
+       file_header_t *file_header = archive_handle->file_header;
+       int dst_fd;
+       int res;
+
+       if (archive_handle->flags & ARCHIVE_CREATE_LEADING_DIRS) {
+               char *name = xstrdup(file_header->name);
+               bb_make_directory(dirname(name), -1, FILEUTILS_RECUR);
+               free(name);
+       }
+
+       /* Check if the file already exists */
+       if (archive_handle->flags & ARCHIVE_EXTRACT_UNCONDITIONAL) {
+               /* Remove the existing entry if it exists */
+               if (((file_header->mode & S_IFMT) != S_IFDIR)
+                && (unlink(file_header->name) == -1)
+                && (errno != ENOENT)
+               ) {
+                       bb_perror_msg_and_die("cannot remove old file %s",
+                                       file_header->name);
+               }
+       }
+       else if (archive_handle->flags & ARCHIVE_EXTRACT_NEWER) {
+               /* Remove the existing entry if its older than the extracted entry */
+               struct stat statbuf;
+               if (lstat(file_header->name, &statbuf) == -1) {
+                       if (errno != ENOENT) {
+                               bb_perror_msg_and_die("cannot stat old file");
+                       }
+               }
+               else if (statbuf.st_mtime <= file_header->mtime) {
+                       if (!(archive_handle->flags & ARCHIVE_EXTRACT_QUIET)) {
+                               bb_error_msg("%s not created: newer or "
+                                       "same age file exists", file_header->name);
+                       }
+                       data_skip(archive_handle);
+                       return;
+               }
+               else if ((unlink(file_header->name) == -1) && (errno != EISDIR)) {
+                       bb_perror_msg_and_die("cannot remove old file %s",
+                                       file_header->name);
+               }
+       }
+
+       /* Handle hard links separately
+        * We identified hard links as regular files of size 0 with a symlink */
+       if (S_ISREG(file_header->mode) && (file_header->link_target)
+        && (file_header->size == 0)
+       ) {
+               /* hard link */
+               res = link(file_header->link_target, file_header->name);
+               if ((res == -1) && !(archive_handle->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)
+                        && !(archive_handle->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->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->flags & ARCHIVE_EXTRACT_QUIET)
+                       ) {
+                               bb_perror_msg("cannot create node %s", file_header->name);
+                       }
+                       break;
+               default:
+                       bb_error_msg_and_die("unrecognized file type");
+               }
+       }
+
+       if (!(archive_handle->flags & ARCHIVE_NOPRESERVE_OWN)) {
+#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->flags & ARCHIVE_NOPRESERVE_PERM)) {
+                       chmod(file_header->name, file_header->mode);
+               }
+               /* same for utime */
+               if (archive_handle->flags & ARCHIVE_PRESERVE_DATE) {
+                       struct utimbuf t;
+                       t.actime = t.modtime = file_header->mtime;
+                       utime(file_header->name, &t);
+               }
+       }
+}
diff --git a/archival/libunarchive/data_extract_to_buffer.c b/archival/libunarchive/data_extract_to_buffer.c
new file mode 100644 (file)
index 0000000..d8fcdf3
--- /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 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..c8895ed
--- /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 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..d9778da
--- /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 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..c1b1273
--- /dev/null
@@ -0,0 +1,769 @@
+/* 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 (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 (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], 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 */
+
+               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 (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 (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 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 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'
+       };
+
+       /* 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']." */
+
+       i = get_bits(bd, 32);
+       if ((unsigned)(i - BZh0 - 1) >= 9) return RETVAL_NOT_BZIP_DATA;
+
+       /* Fourth byte (ascii '1'-'9'), indicates block size in units of 100k of
+          uncompressed data.  Allocate intermediate buffer for block. */
+
+       bd->dbufSize = 100000 * (i - BZh0);
+
+       /* 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 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
+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;
+}
+
+#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 = unpack_bz2_stream(0, 1);
+       char c;
+
+       if (i < 0)
+               fprintf(stderr,"%s\n", bunzip_errors[-i]);
+       else if (read(0, &c, 1))
+               fprintf(stderr,"Trailing garbage ignored\n");
+       return -i;
+}
+#endif
diff --git a/archival/libunarchive/decompress_uncompress.c b/archival/libunarchive/decompress_uncompress.c
new file mode 100644 (file)
index 0000000..8c3c65d
--- /dev/null
@@ -0,0 +1,305 @@
+/* vi: set sw=4 ts=4: */
+#include "libbb.h"
+
+/* uncompress for busybox -- (c) 2002 Robert Griebl
+ *
+ * based on the original compress42.c source
+ * (see disclaimer below)
+ */
+
+/* (N)compress42.c - File compression ala IEEE Computer, Mar 1992.
+ *
+ * Authors:
+ *   Spencer W. Thomas   (decvax!harpo!utah-cs!utah-gr!thomas)
+ *   Jim McKie           (decvax!mcvax!jim)
+ *   Steve Davies        (decvax!vax135!petsd!peora!srd)
+ *   Ken Turkowski       (decvax!decwrl!turtlevax!ken)
+ *   James A. Woods      (decvax!ihnp4!ames!jaw)
+ *   Joe Orost           (decvax!vax135!petsd!joe)
+ *   Dave Mack           (csu@alembic.acs.com)
+ *   Peter Jannesen, Network Communication Systems
+ *                       (peter@ncs.nl)
+ *
+ * marc@suse.de : a small security fix for a buffer overflow
+ *
+ * [... History snipped ...]
+ *
+ */
+
+/* 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
+uncompress(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..5fb7eae
--- /dev/null
@@ -0,0 +1,500 @@
+/* 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_t) + 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)
+{
+       if (ENABLE_FEATURE_CLEAN_UP)
+               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
+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) != 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) != header.dict_size)
+                                               goto bad;
+                                       USE_DESKTOP(total_written += header.dict_size;)
+                               }
+                               len--;
+                       } while (len != 0 && buffer_pos < header.dst_size);
+               }
+       }
+
+       if (full_write(dst_fd, buffer, buffer_pos) != buffer_pos) {
+ bad:
+               rc_free(rc);
+               return -1;
+       }
+       rc_free(rc);
+       USE_DESKTOP(total_written += buffer_pos;)
+       return USE_DESKTOP(total_written) + 0;
+}
diff --git a/archival/libunarchive/decompress_unzip.c b/archival/libunarchive/decompress_unzip.c
new file mode 100644 (file)
index 0000000..a764fbc
--- /dev/null
@@ -0,0 +1,1241 @@
+/* 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) ATTRIBUTE_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)
+{
+       int n;
+       for (n = 0; n < gunzip_outbuf_count; n++) {
+               gunzip_crc = gunzip_crc_table[((int) gunzip_crc ^ (gunzip_window[n])) & 0xff] ^ (gunzip_crc >> 8);
+       }
+       gunzip_bytes_out += gunzip_outbuf_count;
+}
+
+/* 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 != 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
+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 < 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
+       /* gcc 4.2.1 is very clever */
+       memcpy(&res, &bytebuffer[bytebuffer_offset], 2);
+#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
+       memcpy(&res, &bytebuffer[bytebuffer_offset], 4);
+#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_ONLY)
+{
+       union {
+               unsigned char raw[8];
+               struct {
+                       uint8_t gz_method;
+                       uint8_t flags;
+                       //uint32_t mtime; - unused fields
+                       //uint8_t xtra_flags;
+                       //uint8_t os_flags;
+               } formatted; /* packed */
+       } header;
+
+       /*
+        * 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;
+               }
+       }
+
+       /* 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
+unpack_gz_stream(int in, int out)
+{
+       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_ONLY)) {
+               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;
+}
diff --git a/archival/libunarchive/filter_accept_all.c b/archival/libunarchive/filter_accept_all.c
new file mode 100644 (file)
index 0000000..47d771e
--- /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 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..6e571ad
--- /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 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..969dd1e
--- /dev/null
@@ -0,0 +1,44 @@
+/* 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"
+
+/*
+ * Reassign the subarchive metadata parser based on the filename extension
+ * e.g. if its a .tar.gz modify archive_handle->sub_archive to process a .tar.gz
+ * or if its a .tar.bz2 make archive_handle->sub_archive handle that
+ */
+char filter_accept_list_reassign(archive_handle_t *archive_handle)
+{
+       /* Check the file entry is in the accept list */
+       if (find_list_entry(archive_handle->accept, archive_handle->file_header->name)) {
+               const char *name_ptr;
+
+               /* Extract the last 2 extensions */
+               name_ptr = strrchr(archive_handle->file_header->name, '.');
+
+               /* Modify the subarchive handler based on the extension */
+#if ENABLE_FEATURE_DEB_TAR_GZ
+               if (strcmp(name_ptr, ".gz") == 0) {
+                       archive_handle->action_data_subarchive = get_header_tar_gz;
+                       return EXIT_SUCCESS;
+               }
+#endif
+#if ENABLE_FEATURE_DEB_TAR_BZ2
+               if (strcmp(name_ptr, ".bz2") == 0) {
+                       archive_handle->action_data_subarchive = get_header_tar_bz2;
+                       return EXIT_SUCCESS;
+               }
+#endif
+               if (ENABLE_FEATURE_DEB_TAR_LZMA && !strcmp(name_ptr, ".lzma")) {
+                       archive_handle->action_data_subarchive = get_header_tar_lzma;
+                       return EXIT_SUCCESS;
+               }
+       }
+       return EXIT_FAILURE;
+}
diff --git a/archival/libunarchive/filter_accept_reject_list.c b/archival/libunarchive/filter_accept_reject_list.c
new file mode 100644 (file)
index 0000000..439ba20
--- /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 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..7540589
--- /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 *find_list_entry(const llist_t *list, const char *filename)
+{
+       while (list) {
+               if (fnmatch(list->data, filename, 0) == 0) {
+                       return list;
+               }
+               list = list->link;
+       }
+       return NULL;
+}
+
+/* Same, but compares only path components present in pattern
+ * (extra trailing path components in filename are assumed to match)
+ */
+const llist_t *find_list_entry2(const llist_t *list, const char *filename)
+{
+       char buf[PATH_MAX];
+       int pattern_slash_cnt;
+       const char *c;
+       char *d;
+
+       while (list) {
+               c = list->data;
+               pattern_slash_cnt = 0;
+               while (*c)
+                       if (*c++ == '/') pattern_slash_cnt++;
+               c = filename;
+               d = buf;
+               /* paranoia is better 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..88c0220
--- /dev/null
@@ -0,0 +1,126 @@
+/* vi: set sw=4 ts=4: */
+/* Copyright 2001 Glenn McGrath.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+char get_header_ar(archive_handle_t *archive_handle)
+{
+       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 (archive_handle->sub_archive) {
+                       while (archive_handle->action_data_subarchive(archive_handle->sub_archive) == EXIT_SUCCESS)
+                               /* repeat */;
+               } else {
+                       archive_handle->action_data(archive_handle);
+               }
+       } else {
+               data_skip(archive_handle);
+       }
+
+       archive_handle->offset += typed->size;
+       /* Set the file pointer to the correct spot, we may have been reading a compressed file */
+       lseek(archive_handle->src_fd, archive_handle->offset, SEEK_SET);
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/libunarchive/get_header_cpio.c b/archival/libunarchive/get_header_cpio.c
new file mode 100644 (file)
index 0000000..3f51355
--- /dev/null
@@ -0,0 +1,161 @@
+/* vi: set sw=4 ts=4: */
+/* Copyright 2002 Laurence Anderson
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+typedef struct hardlinks_s {
+       char *name;
+       int inode;
+       struct hardlinks_s *next;
+} hardlinks_t;
+
+char get_header_cpio(archive_handle_t *archive_handle)
+{
+       static hardlinks_t *saved_hardlinks = NULL;
+       static unsigned pending_hardlinks = 0;
+       static int inode;
+
+       file_header_t *file_header = archive_handle->file_header;
+       char cpio_header[110];
+       int namesize;
+       char dummy[16];
+       int major, minor, nlink;
+
+       if (pending_hardlinks) { /* Deal with any pending hardlinks */
+               hardlinks_t *tmp, *oldtmp;
+
+               tmp = saved_hardlinks;
+               oldtmp = NULL;
+
+               file_header->link_target = file_header->name;
+               file_header->size = 0;
+
+               while (tmp) {
+                       if (tmp->inode != inode) {
+                               tmp = tmp->next;
+                               continue;
+                       }
+
+                       file_header->name = tmp->name;
+
+                       if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) {
+                               archive_handle->action_data(archive_handle);
+                               archive_handle->action_header(archive_handle->file_header);
+                       }
+
+                       pending_hardlinks--;
+
+                       oldtmp = tmp;
+                       tmp = tmp->next;
+                       free(oldtmp->name);
+                       free(oldtmp);
+                       if (oldtmp == saved_hardlinks)
+                               saved_hardlinks = tmp;
+               }
+
+               file_header->name = file_header->link_target;
+
+               if (pending_hardlinks > 1) {
+                       bb_error_msg("error resolving hardlink: archive made by GNU cpio 2.0-2.2?");
+               }
+
+               /* No more pending hardlinks, read next file entry */
+               pending_hardlinks = 0;
+       }
+
+       /* There can be padding before archive header */
+       data_align(archive_handle, 4);
+
+       if (archive_xread_all_eof(archive_handle, (unsigned char*)cpio_header, 110) == 0) {
+               return EXIT_FAILURE;
+       }
+       archive_handle->offset += 110;
+
+       if (strncmp(&cpio_header[0], "07070", 5) != 0
+        || (cpio_header[5] != '1' && cpio_header[5] != '2')
+       ) {
+               bb_error_msg_and_die("unsupported cpio format, use newc or crc");
+       }
+
+       {
+               unsigned long tmpsize;
+               sscanf(cpio_header, "%6c%8x%8x%8x%8x%8x%8lx%8lx%16c%8x%8x%8x%8c",
+                   dummy, &inode, (unsigned int*)&file_header->mode,
+                   (unsigned int*)&file_header->uid, (unsigned int*)&file_header->gid,
+                   &nlink, &file_header->mtime, &tmpsize,
+                   dummy, &major, &minor, &namesize, dummy);
+               file_header->size = tmpsize;
+       }
+
+       free(file_header->name);
+       file_header->name = xzalloc(namesize + 1);
+       /* Read in filename */
+       xread(archive_handle->src_fd, file_header->name, namesize);
+       archive_handle->offset += namesize;
+
+       /* Update offset amount and skip padding before file contents */
+       data_align(archive_handle, 4);
+
+       if (strcmp(file_header->name, "TRAILER!!!") == 0) {
+               /* Always round up */
+               printf("%d blocks\n", (int) (archive_handle->offset % 512 ?
+                                            archive_handle->offset / 512 + 1 :
+                                            archive_handle->offset / 512
+                                           ));
+               if (saved_hardlinks) { /* Bummer - we still have unresolved hardlinks */
+                       hardlinks_t *tmp = saved_hardlinks;
+                       hardlinks_t *oldtmp = NULL;
+                       while (tmp) {
+                               bb_error_msg("%s not created: cannot resolve hardlink", tmp->name);
+                               oldtmp = tmp;
+                               tmp = tmp->next;
+                               free(oldtmp->name);
+                               free(oldtmp);
+                       }
+                       saved_hardlinks = NULL;
+                       pending_hardlinks = 0;
+               }
+               return EXIT_FAILURE;
+       }
+
+       if (S_ISLNK(file_header->mode)) {
+               file_header->link_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 */
+       } else {
+               file_header->link_target = NULL;
+       }
+       if (nlink > 1 && !S_ISDIR(file_header->mode)) {
+               if (file_header->size == 0) { /* Put file on a linked list for later */
+                       hardlinks_t *new = xmalloc(sizeof(hardlinks_t));
+                       new->next = saved_hardlinks;
+                       new->inode = inode;
+                       /* name current allocated, freed later */
+                       new->name = file_header->name;
+                       file_header->name = NULL;
+                       saved_hardlinks = new;
+                       return EXIT_SUCCESS; /* Skip this one */
+               }
+               /* Found the file with data in */
+               pending_hardlinks = nlink;
+       }
+       file_header->device = makedev(major, minor);
+
+       if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) {
+               archive_handle->action_data(archive_handle);
+               archive_handle->action_header(archive_handle->file_header);
+       } else {
+               data_skip(archive_handle);
+       }
+
+       archive_handle->offset += file_header->size;
+
+       free(file_header->link_target);
+
+       return EXIT_SUCCESS;
+}
diff --git a/archival/libunarchive/get_header_tar.c b/archival/libunarchive/get_header_tar.c
new file mode 100644 (file)
index 0000000..b1a797a
--- /dev/null
@@ -0,0 +1,368 @@
+/* vi: set sw=4 ts=4: */
+/* Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ *  FIXME:
+ *    In privileged mode if uname and gname map to a uid and gid then use the
+ *    mapped value instead of the uid/gid values in tar header
+ *
+ *  References:
+ *    GNU tar and star man pages,
+ *    Opengroup's ustar interchange format,
+ *     http://www.opengroup.org/onlinepubs/007904975/utilities/pax.html
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
+static char *longname;
+static char *linkname;
+#else
+enum {
+       longname = 0,
+       linkname = 0,
+};
+#endif
+
+/* NB: _DESTROYS_ str[len] character! */
+static unsigned long long getOctal(char *str, int len)
+{
+       unsigned long long v;
+       /* Actually, tar header allows leading spaces also.
+        * Oh well, we will be liberal and skip this...
+        * The only downside probably is that we allow "-123" too :)
+       if (*str < '0' || *str > '7')
+               bb_error_msg_and_die("corrupted octal value in tar header");
+       */
+       str[len] = '\0';
+       v = strtoull(str, &str, 8);
+       if (*str && (!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 get_header_tar(archive_handle_t *archive_handle)
+{
+       static smallint end;
+#if ENABLE_FEATURE_TAR_AUTODETECT
+       static smallint not_first;
+#endif
+
+       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 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 */
+       } tar;
+       char *cp;
+       int i, sum_u, sum;
+#if ENABLE_FEATURE_TAR_OLDSUN_COMPATIBILITY
+       int sum_s;
+#endif
+       int parse_names;
+
+       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
+       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 tar will exit with error code 0.
+        * We will mimic exit(0), 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;
+       if (i != 512)
+               bb_error_msg_and_die("short read");
+#else
+       xread(archive_handle->src_fd, &tar, 512);
+#endif
+       archive_handle->offset += 512;
+
+       /* If there is no filename its an empty header */
+       if (tar.name[0] == 0 && tar.prefix[0] == 0) {
+               if (end) {
+                       /* This is the second consecutive empty header! End of archive!
+                        * Read until the end to empty the pipe from gz or bz2
+                        */
+                       while (full_read(archive_handle->src_fd, &tar, 512) == 512)
+                               continue;
+                       return EXIT_FAILURE;
+               }
+               end = 1;
+               return EXIT_SUCCESS;
+       }
+       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 (*get_header_ptr)(archive_handle_t *);
+
+               /* tar gz/bz autodetect: check for gz/bz2 magic.
+                * If it is the very first block, and we see the magic,
+                * we can switch to get_header_tar_gz/bz2/lzma().
+                * Needs seekable fd. I wish recv(MSG_PEEK) would work
+                * on any fd... */
+               if (not_first)
+                       goto err;
+#if ENABLE_FEATURE_TAR_GZIP
+               if (tar.name[0] == 0x1f && tar.name[1] == 0x8b) { /* gzip */
+                       get_header_ptr = get_header_tar_gz;
+               } else
+#endif
+#if ENABLE_FEATURE_TAR_BZIP2
+               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;
+               if (lseek(archive_handle->src_fd, -512, 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");
+       }
+
+#if ENABLE_FEATURE_TAR_AUTODETECT
+       not_first = 1;
+#endif
+
+       /* 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 (!linkname && parse_names && tar.linkname[0]) {
+               /* we trash magic[0] here, it's ok */
+               tar.linkname[sizeof(tar.linkname)] = '\0';
+               file_header->link_target = xstrdup(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);
+       file_header->size = GET_OCTAL(tar.size);
+       file_header->gid = GET_OCTAL(tar.gid);
+       file_header->uid = GET_OCTAL(tar.uid);
+       /* Set bits 0-11 of the files mode */
+       file_header->mode = 07777 & GET_OCTAL(tar.mode);
+
+       file_header->name = NULL;
+       if (!longname && parse_names) {
+               /* we trash mode[0] here, it's ok */
+               tar.name[sizeof(tar.name)] = '\0';
+               if (tar.prefix[0]) {
+                       /* and padding[0] */
+                       tar.prefix[sizeof(tar.prefix)] = '\0';
+                       file_header->name = concat_path_file(tar.prefix, tar.name);
+               } else
+                       file_header->name = xstrdup(tar.name);
+       }
+
+       /* Set bits 12-15 of the files mode */
+       /* (typeflag was not trashed because chksum does not use getOctal) */
+       switch (tar.typeflag) {
+       /* busybox identifies hard links as being regular files with 0 size and a link name */
+       case '1':
+               file_header->mode |= S_IFREG;
+               break;
+       case '7':
+       /* case 0: */
+       case '0':
+#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY
+               if (last_char_is(file_header->name, '/')) {
+                       file_header->mode |= S_IFDIR;
+               } else
+#endif
+               file_header->mode |= S_IFREG;
+               break;
+       case '2':
+               file_header->mode |= S_IFLNK;
+               break;
+       case '3':
+               file_header->mode |= S_IFCHR;
+               break;
+       case '4':
+               file_header->mode |= S_IFBLK;
+               break;
+       case '5':
+               file_header->mode |= S_IFDIR;
+               break;
+       case '6':
+               file_header->mode |= S_IFIFO;
+               break;
+#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
+       case 'L':
+               /* free: paranoia: tar with several consecutive longnames */
+               free(longname);
+               /* For paranoia reasons we allocate extra NUL char */
+               longname = xzalloc(file_header->size + 1);
+               /* We read ASCIZ string, including NUL */
+               xread(archive_handle->src_fd, longname, file_header->size);
+               archive_handle->offset += file_header->size;
+               /* return get_header_tar(archive_handle); */
+               /* gcc 4.1.1 didn't optimize it into jump */
+               /* so we will do it ourself, this also saves stack */
+               goto again;
+       case 'K':
+               free(linkname);
+               linkname = xzalloc(file_header->size + 1);
+               xread(archive_handle->src_fd, linkname, file_header->size);
+               archive_handle->offset += file_header->size;
+               /* return get_header_tar(archive_handle); */
+               goto again;
+       case 'D':       /* GNU dump dir */
+       case 'M':       /* Continuation of multi volume archive */
+       case 'N':       /* Old GNU for names > 100 characters */
+       case 'S':       /* Sparse file */
+       case 'V':       /* Volume header */
+#endif
+       case 'g':       /* pax global header */
+       case 'x': {     /* pax extended header */
+               off_t sz;
+               bb_error_msg("warning: skipping header '%c'", tar.typeflag);
+               sz = (file_header->size + 511) & ~(off_t)511;
+               archive_handle->offset += sz;
+               sz >>= 9; /* sz /= 512 but w/o contortions for signed div */
+               while (sz--)
+                       xread(archive_handle->src_fd, &tar, 512);
+               /* return get_header_tar(archive_handle); */
+               goto again_after_align;
+       }
+       default:
+               bb_error_msg_and_die("unknown typeflag: 0x%x", tar.typeflag);
+       }
+
+#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
+       if (longname) {
+               file_header->name = longname;
+               longname = NULL;
+       }
+       if (linkname) {
+               file_header->link_target = linkname;
+               linkname = NULL;
+       }
+#endif
+       if (!strncmp(file_header->name, "/../"+1, 3)
+        || strstr(file_header->name, "/../")
+       ) {
+               bb_error_msg_and_die("name with '..' encountered: '%s'",
+                               file_header->name);
+       }
+
+       /* Strip trailing '/' in directories */
+       /* Must be done after mode is set as '/' is used to check if 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->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)! */
+#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..c2cbaff
--- /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 get_header_tar_bz2(archive_handle_t *archive_handle)
+{
+       /* Can't lseek over pipes */
+       archive_handle->seek = seek_by_read;
+
+       archive_handle->src_fd = open_transformer(archive_handle->src_fd, unpack_bz2_stream, "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..9772e33
--- /dev/null
@@ -0,0 +1,35 @@
+/* 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 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);
+       if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) {
+               bb_error_msg_and_die("invalid gzip magic");
+       }
+#endif
+
+       archive_handle->src_fd = 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..c859dcc
--- /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 get_header_tar_lzma(archive_handle_t * archive_handle)
+{
+       /* Can't lseek over pipes */
+       archive_handle->seek = seek_by_read;
+
+       archive_handle->src_fd = open_transformer(archive_handle->src_fd, 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..8cb8f40
--- /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 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..ef2172b
--- /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 header_skip(const file_header_t *file_header ATTRIBUTE_UNUSED)
+{
+}
diff --git a/archival/libunarchive/header_verbose_list.c b/archival/libunarchive/header_verbose_list.c
new file mode 100644 (file)
index 0000000..ea623ed
--- /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 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 %9u %4u-%02u-%02u %02u:%02u:%02u %s",
+               bb_mode_string(file_header->mode),
+               user,
+               group,
+               (unsigned int) 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..309d329
--- /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 *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..3c551de
--- /dev/null
@@ -0,0 +1,62 @@
+/* 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.
+ */
+int open_transformer(int src_fd,
+       USE_DESKTOP(long long) int (*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();
+#else
+       pid = vfork();
+#endif
+       if (pid == -1)
+               bb_perror_msg_and_die("fork failed");
+
+       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(src_fd, fd_pipe.wr);
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       close(fd_pipe.wr); /* Send EOF */
+                       close(src_fd);
+               }
+               exit(0);
+#else
+               {
+                       char *argv[4];
+                       xmove_fd(src_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("exec failed");
+               }
+#endif
+               /* notreached */
+       }
+
+       /* parent process */
+       close(fd_pipe.wr); /* Don't want to write to the child */
+
+       return fd_pipe.rd;
+}
diff --git a/archival/libunarchive/seek_by_jump.c b/archival/libunarchive/seek_by_jump.c
new file mode 100644 (file)
index 0000000..8b5f3e8
--- /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 seek_by_jump(const archive_handle_t *archive_handle, unsigned amount)
+{
+       if (lseek(archive_handle->src_fd, (off_t) amount, SEEK_CUR) == (off_t) -1) {
+#if ENABLE_FEATURE_UNARCHIVE_TAPE
+               if (errno == ESPIPE) {
+                       seek_by_read(archive_handle, amount);
+               } else
+#endif
+                       bb_perror_msg_and_die("seek failure");
+       }
+}
diff --git a/archival/libunarchive/seek_by_read.c b/archival/libunarchive/seek_by_read.c
new file mode 100644 (file)
index 0000000..1f2b805
--- /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 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..fc1820b
--- /dev/null
@@ -0,0 +1,20 @@
+/* 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 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);
+}
diff --git a/archival/rpm.c b/archival/rpm.c
new file mode 100644 (file)
index 0000000..41b8c81
--- /dev/null
@@ -0,0 +1,398 @@
+/* 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 (!argc) 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 (*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->flags |= ARCHIVE_PRESERVE_DATE;
+       archive_handle->flags |= ARCHIVE_CREATE_LEADING_DIRS;
+       archive_handle->src_fd = fd;
+       archive_handle->offset = 0;
+
+       xread(archive_handle->src_fd, &magic, 2);
+#if BB_MMU
+       xformer = unpack_gz_stream;
+#else
+       xformer_prog = "gunzip";
+#endif
+       if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) {
+               if (ENABLE_FEATURE_RPM_BZ2
+                && (magic[0] == 0x42) && (magic[1] == 0x5a)) {
+#if BB_MMU
+                       xformer = unpack_bz2_stream;
+#else
+                       xformer_prog = "bunzip2";
+#endif
+       /* We can do better, need modifying unpack_bz2_stream to not require
+        * first 2 bytes. Not very hard to do... I mean, TODO :) */
+                       xlseek(archive_handle->src_fd, -2, SEEK_CUR);
+               } else
+                       bb_error_msg_and_die("no gzip"
+                               USE_FEATURE_RPM_BZ2("/bzip")
+                               " magic");
+       } 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 */
+       archive_handle->src_fd = 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(struct rpmtag *));
+       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;
+               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(rpm_index));
+                       xread(fd, tmpindex, sizeof(rpm_index));
+                       tmpindex->tag = ntohl(tmpindex->tag);
+                       tmpindex->type = ntohl(tmpindex->type);
+                       tmpindex->count = ntohl(tmpindex->count);
+                       tmpindex->offset = storepos + ntohl(tmpindex->offset);
+                       if (pass==0)
+                               tmpindex->tag -= 743;
+               }
+               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(struct rpmtag *)); /* realloc tags to save space */
+       *num_tags = tagindex;
+       return tags; /* All done, leave the file at the start of the gzipped cpio archive */
+}
+
+static int bsearch_rpmtag(const void *key, const void *item)
+{
+       int *tag = (int *)key;
+       rpm_index **tmp = (rpm_index **) item;
+       return (*tag - tmp[0]->tag);
+}
+
+static int rpm_getcount(int tag)
+{
+       rpm_index **found;
+       found = bsearch(&tag, mytags, tagcount, sizeof(struct rpmtag *), bsearch_rpmtag);
+       if (!found)
+               return 0;
+       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)
+{
+       int uid, gid;
+       uid = xuname2uid(rpm_getstr(TAG_FILEUSERNAME, fileref));
+       gid = xgroup2gid(rpm_getstr(TAG_FILEGROUPNAME, fileref));
+       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..329f8f7
--- /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..e790f28
--- /dev/null
@@ -0,0 +1,982 @@
+/* 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 <getopt.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_TAR_GZIP && !ENABLE_FEATURE_TAR_BZIP2
+/* 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 (tbInfo->tarFd == STDOUT_FILENO)     /* If the archive goes to stdout, verbose to stderr */
+                       vbFd = stderr;
+               /* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */
+               /* We don't have such excesses here: for us "v" == "vv" */
+               /* '/' is probably a GNUism */
+               fprintf(vbFd, "%s%s\n", header_name,
+                               S_ISDIR(statbuf->st_mode) ? "/" : "");
+       }
+
+       return TRUE;
+}
+
+#if ENABLE_FEATURE_TAR_FROM
+static int exclude_file(const llist_t *excluded_files, const char *file)
+{
+       while (excluded_files) {
+               if (excluded_files->data[0] == '/') {
+                       if (fnmatch(excluded_files->data, file,
+                                               FNM_PATHNAME | FNM_LEADING_DIR) == 0)
+                               return 1;
+               } else {
+                       const char *p;
+
+                       for (p = file; p[0] != '\0'; p++) {
+                               if ((p == file || p[-1] == '/') && p[0] != '/' &&
+                                       fnmatch(excluded_files->data, p,
+                                                       FNM_PATHNAME | FNM_LEADING_DIR) == 0)
+                                       return 1;
+                       }
+               }
+               excluded_files = excluded_files->link;
+       }
+
+       return 0;
+}
+#else
+#define exclude_file(excluded_files, file) 0
+#endif
+
+static int writeFileToTarball(const char *fileName, struct stat *statbuf,
+                       void *userData, int depth ATTRIBUTE_UNUSED)
+{
+       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;
+}
+
+static int writeTarFile(const int tar_fd, const int verboseFlag,
+       const unsigned long dereferenceFlag, const llist_t *include,
+       const llist_t *exclude, const int gzip)
+{
+       pid_t gzipPid = 0;
+       int errorFlag = FALSE;
+       struct TarBallInfo tbInfo;
+
+       tbInfo.hlInfoHead = NULL;
+
+       fchmod(tar_fd, 0644);
+       tbInfo.tarFd = tar_fd;
+       tbInfo.verboseFlag = verboseFlag;
+
+       /* Store the stat info for the tarball's file, so
+        * can avoid including the tarball into itself....  */
+       if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0)
+               bb_perror_msg_and_die("cannot stat tar file");
+
+#if ENABLE_FEATURE_TAR_GZIP || ENABLE_FEATURE_TAR_BZIP2
+       if (gzip) {
+#if ENABLE_FEATURE_TAR_GZIP && ENABLE_FEATURE_TAR_BZIP2
+               const char *zip_exec = (gzip == 1) ? "gzip" : "bzip2";
+#elif ENABLE_FEATURE_TAR_GZIP
+               const char *zip_exec = "gzip";
+#else /* only ENABLE_FEATURE_TAR_BZIP2 */
+               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;
+#if WAIT_FOR_CHILD
+               struct fd_pair gzipStatusPipe;
+#endif
+               struct fd_pair gzipDataPipe;
+               xpiped_pair(gzipDataPipe);
+#if WAIT_FOR_CHILD
+               xpiped_pair(gzipStatusPipe);
+#endif
+
+               signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */
+
+#if defined(__GNUC__) && __GNUC__
+               /* Avoid vfork clobbering */
+               (void) &include;
+               (void) &errorFlag;
+               (void) &zip_exec;
+#endif
+
+               gzipPid = vfork();
+               if (gzipPid < 0)
+                       bb_perror_msg_and_die("vfork gzip");
+
+               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(tbInfo.tarFd, 1);
+                       /* exec gzip/bzip2 program/applet */
+                       BB_EXECLP(zip_exec, zip_exec, "-f", NULL);
+                       vfork_exec_errno = errno;
+                       _exit(1);
+               }
+
+               /* parent */
+               xmove_fd(gzipDataPipe.wr, tbInfo.tarFd);
+               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
+
+       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 (gzipPid) {
+               int status;
+               if (safe_waitpid(gzipPid, &status, 0) == -1)
+                       bb_perror_msg("waitpid");
+               else if (!WIFEXITED(status) || WEXITSTATUS(status))
+                       /* gzip was killed or has exited with nonzero! */
+                       errorFlag = TRUE;
+       }
+       return errorFlag;
+}
+#else
+int writeTarFile(const int tar_fd, const int verboseFlag,
+       const unsigned long dereferenceFlag, const llist_t *include,
+       const llist_t *exclude, const int gzip);
+#endif /* FEATURE_TAR_CREATE */
+
+#if ENABLE_FEATURE_TAR_FROM
+static llist_t *append_file_list_to_list(llist_t *list)
+{
+       FILE *src_stream;
+       llist_t *cur = list;
+       llist_t *tmp;
+       char *line;
+       llist_t *newlist = NULL;
+
+       while (cur) {
+               src_stream = xfopen(cur->data, "r");
+               tmp = cur;
+               cur = cur->link;
+               free(tmp);
+               while ((line = xmalloc_getline(src_stream)) != NULL) {
+                       /* kill trailing '/' unless the string is just "/" */
+                       char *cp = last_char_is(line, '/');
+                       if (cp > line)
+                               *cp = '\0';
+                       llist_add_to(&newlist, line);
+               }
+               fclose(src_stream);
+       }
+       return newlist;
+}
+#else
+#define append_file_list_to_list(x) 0
+#endif
+
+#if ENABLE_FEATURE_TAR_COMPRESS
+static char get_header_tar_Z(archive_handle_t *archive_handle)
+{
+       /* Can't lseek over pipes */
+       archive_handle->seek = seek_by_read;
+
+       /* do the decompression, and cleanup */
+       if (xread_char(archive_handle->src_fd) != 0x1f
+        || xread_char(archive_handle->src_fd) != 0x9d
+       ) {
+               bb_error_msg_and_die("invalid magic");
+       }
+
+       archive_handle->src_fd = open_transformer(archive_handle->src_fd, uncompress, "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_TAR_BZIP2(   OPTBIT_BZIP2       ,)
+       USE_FEATURE_TAR_LZMA(    OPTBIT_LZMA        ,)
+       USE_FEATURE_TAR_FROM(    OPTBIT_INCLUDE_FROM,)
+       USE_FEATURE_TAR_FROM(    OPTBIT_EXCLUDE_FROM,)
+       USE_FEATURE_TAR_GZIP(    OPTBIT_GZIP        ,)
+       USE_FEATURE_TAR_COMPRESS(OPTBIT_COMPRESS    ,)
+       OPTBIT_NOPRESERVE_OWN,
+       OPTBIT_NOPRESERVE_PERM,
+       OPT_TEST         = 1 << 0, // t
+       OPT_EXTRACT      = 1 << 1, // x
+       OPT_BASEDIR      = 1 << 2, // C
+       OPT_TARNAME      = 1 << 3, // f
+       OPT_2STDOUT      = 1 << 4, // O
+       OPT_P            = 1 << 5, // p
+       OPT_VERBOSE      = 1 << 6, // v
+       OPT_KEEP_OLD     = 1 << 7, // k
+       OPT_CREATE       = USE_FEATURE_TAR_CREATE(  (1<<OPTBIT_CREATE      )) + 0, // c
+       OPT_DEREFERENCE  = USE_FEATURE_TAR_CREATE(  (1<<OPTBIT_DEREFERENCE )) + 0, // h
+       OPT_BZIP2        = USE_FEATURE_TAR_BZIP2(   (1<<OPTBIT_BZIP2       )) + 0, // j
+       OPT_LZMA         = USE_FEATURE_TAR_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_TAR_GZIP(    (1<<OPTBIT_GZIP        )) + 0, // z
+       OPT_COMPRESS     = USE_FEATURE_TAR_COMPRESS((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_TAR_BZIP2
+       "bzip2\0"               No_argument       "j"
+# endif
+# if ENABLE_FEATURE_TAR_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_TAR_GZIP
+       "gzip\0"                No_argument       "z"
+# endif
+# if ENABLE_FEATURE_TAR_COMPRESS
+       "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, char **argv)
+{
+       char (*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->flags = ARCHIVE_CREATE_LEADING_DIRS
+                         | ARCHIVE_PRESERVE_DATE
+                         | ARCHIVE_EXTRACT_UNCONDITIONAL;
+
+       /* Prepend '-' to the first argument if required */
+       opt_complementary = "--:" // first arg is options
+               "tt:vv:" // count -t,-v
+               "?:" // bail out with usage instead of error return
+               "X::T::" // cumulative lists
+#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_TAR_BZIP2(   "j"   )
+               USE_FEATURE_TAR_LZMA(    "a"   )
+               USE_FEATURE_TAR_FROM(    "T:X:")
+               USE_FEATURE_TAR_GZIP(    "z"   )
+               USE_FEATURE_TAR_COMPRESS("Z"   )
+               , &base_dir // -C dir
+               , &tar_filename // -f filename
+               USE_FEATURE_TAR_FROM(, &(tar_handle->accept)) // T
+               USE_FEATURE_TAR_FROM(, &(tar_handle->reject)) // X
+#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
+               );
+
+       if (verboseFlag) tar_handle->action_header = header_verbose_list;
+       if (verboseFlag == 1) tar_handle->action_header = header_list;
+
+       if (opt & OPT_EXTRACT)
+               tar_handle->action_data = data_extract_all;
+
+       if (opt & OPT_2STDOUT)
+               tar_handle->action_data = data_extract_to_stdout;
+
+       if (opt & OPT_KEEP_OLD)
+               tar_handle->flags &= ~ARCHIVE_EXTRACT_UNCONDITIONAL;
+
+       if (opt & OPT_NOPRESERVE_OWN)
+               tar_handle->flags |= ARCHIVE_NOPRESERVE_OWN;
+
+       if (opt & OPT_NOPRESERVE_PERM)
+               tar_handle->flags |= ARCHIVE_NOPRESERVE_PERM;
+
+       if (opt & OPT_GZIP)
+               get_header_ptr = get_header_tar_gz;
+
+       if (opt & OPT_BZIP2)
+               get_header_ptr = get_header_tar_bz2;
+
+       if (opt & OPT_LZMA)
+               get_header_ptr = get_header_tar_lzma;
+
+       if (opt & OPT_COMPRESS)
+               get_header_ptr = get_header_tar_Z;
+
+#if ENABLE_FEATURE_TAR_FROM
+       tar_handle->reject = append_file_list_to_list(tar_handle->reject);
+#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
+
+       /* Check if we are reading from stdin */
+       if (argv[optind] && *argv[optind] == '-') {
+               /* Default is to read from stdin, so just skip to next arg */
+               optind++;
+       }
+
+       /* Setup an array of filenames to work with */
+       /* TODO: This is the same as in ar, separate function ? */
+       while (optind < argc) {
+               /* kill trailing '/' unless the string is just "/" */
+               char *cp = last_char_is(argv[optind], '/');
+               if (cp > argv[optind])
+                       *cp = '\0';
+               llist_add_to_end(&tar_handle->accept, argv[optind]);
+               optind++;
+       }
+
+       if (tar_handle->accept || tar_handle->reject)
+               tar_handle->filter = filter_accept_reject_list;
+
+       /* Open the tar file */
+       {
+               FILE *tar_stream;
+               int flags;
+
+               if (opt & OPT_CREATE) {
+                       /* Make sure there is at least one file to tar up.  */
+                       if (tar_handle->accept == NULL)
+                               bb_error_msg_and_die("empty archive");
+
+                       tar_stream = stdout;
+                       /* Mimicking GNU tar 1.15.1: */
+                       flags = O_WRONLY|O_CREAT|O_TRUNC;
+               /* was doing unlink; open(O_WRONLY|O_CREAT|O_EXCL); why? */
+               } else {
+                       tar_stream = stdin;
+                       flags = O_RDONLY;
+               }
+
+               if (LONE_DASH(tar_filename)) {
+                       tar_handle->src_fd = fileno(tar_stream);
+                       tar_handle->seek = seek_by_read;
+               } else {
+                       tar_handle->src_fd = xopen(tar_filename, flags);
+               }
+       }
+
+       if (base_dir)
+               xchdir(base_dir);
+
+#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_TAR_GZIP || ENABLE_FEATURE_TAR_BZIP2
+               int zipMode = 0;
+               if (ENABLE_FEATURE_TAR_GZIP && (opt & OPT_GZIP))
+                       zipMode = 1;
+               if (ENABLE_FEATURE_TAR_BZIP2 && (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..c7d39da
--- /dev/null
@@ -0,0 +1,408 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini unzip implementation for busybox
+ *
+ * Copyright (C) 2004 by Ed Clark
+ *
+ * Loosely based on original busybox unzip applet by Laurence Anderson.
+ * All options and features should work in this version.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+/* For reference see
+ * http://www.pkware.com/company/standards/appnote/
+ * http://www.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip
+ */
+
+/* TODO
+ * Endian issues
+ * Zip64 + other methods
+ * Improve handling of zip format, ie.
+ * - deferred CRC, comp. & uncomp. lengths (zip header flags bit 3)
+ * - unix file permissions, etc.
+ * - central directory
+ */
+
+#include "libbb.h"
+#include "unarchive.h"
+
+enum {
+#if BB_BIG_ENDIAN
+       ZIP_FILEHEADER_MAGIC = 0x504b0304,
+       ZIP_CDS_MAGIC        = 0x504b0102,
+       ZIP_CDS_END_MAGIC    = 0x504b0506,
+       ZIP_DD_MAGIC         = 0x504b0708,
+#else
+       ZIP_FILEHEADER_MAGIC = 0x04034b50,
+       ZIP_CDS_MAGIC        = 0x02014b50,
+       ZIP_CDS_END_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 ATTRIBUTE_PACKED;        /* 10-13 */
+               uint32_t cmpsize ATTRIBUTE_PACKED;      /* 14-17 */
+               uint32_t ucmpsize ATTRIBUTE_PACKED;     /* 18-21 */
+               uint16_t filename_len;                  /* 22-23 */
+               uint16_t extra_len;                     /* 24-25 */
+       } formatted ATTRIBUTE_PACKED;
+} zip_header_t; /* ATTRIBUTE_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_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)
+
+static void unzip_skip(int fd, off_t skip)
+{
+       bb_copyfd_exact_size(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 src_fd, int dst_fd)
+{
+       if (zip_header->formatted.method == 0) {
+               /* Method 0 - stored (not compressed) */
+               off_t size = zip_header->formatted.ucmpsize;
+               if (size)
+                       bb_copyfd_exact_size(src_fd, dst_fd, size);
+       } else {
+               /* Method 8 - inflate */
+               inflate_unzip_result res;
+               if (inflate_unzip(&res, zip_header->formatted.cmpsize, src_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;
+       unsigned total_size;
+       unsigned total_entries;
+       int src_fd = -1;
+       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)) {
+               src_fd = STDIN_FILENO;
+               /* 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);
+
+               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);
+               }
+       }
+
+       /* 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;
+       while (1) {
+               uint32_t magic;
+
+               /* Check magic number */
+               xread(src_fd, &magic, 4);
+               if (magic == ZIP_CDS_MAGIC)
+                       break;
+               if (magic != ZIP_FILEHEADER_MAGIC)
+                       bb_error_msg_and_die("invalid zip magic %08X", magic);
+
+               /* Read the file header */
+               xread(src_fd, zip_header.raw, ZIP_HEADER_LEN);
+               FIX_ENDIANNESS(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);
+               }
+
+               /* Read filename */
+               free(dst_fn);
+               dst_fn = xzalloc(zip_header.formatted.filename_len + 1);
+               xread(src_fd, dst_fn, zip_header.formatted.filename_len);
+
+               /* Skip extra header bytes */
+               unzip_skip(src_fd, zip_header.formatted.extra_len);
+
+               /* 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;
+                                       total_entries++;
+                               } 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("cannot 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("cannot stat '%s'",dst_fn);
+                                       }
+                                       i = 'y';
+                               } else { /* File already exists */
+                                       if (overwrite == O_NEVER) {
+                                               i = 'n';
+                                       } else if (S_ISREG(stat_buf.st_mode)) { /* File is regular file */
+                                               if (overwrite == O_ALWAYS) {
+                                                       i = 'y';
+                                               } else {
+                                                       printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn);
+                                                       if (!fgets(key_buf, sizeof(key_buf), stdin)) {
+                                                               bb_perror_msg_and_die("cannot read input");
+                                                       }
+                                                       i = key_buf[0];
+                                               }
+                                       } else { /* File is not regular file */
+                                               bb_error_msg_and_die("'%s' exists but is not regular file",dst_fn);
+                                       }
+                               }
+                       }
+               }
+
+               switch (i) {
+               case 'A':
+                       overwrite = O_ALWAYS;
+               case 'y': /* Open file and fall into unzip */
+                       unzip_create_leading_dirs(dst_fn);
+                       dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC);
+               case -1: /* Unzip */
+                       if (verbose) {
+                               printf("  inflating: %s\n", dst_fn);
+                       }
+                       unzip_extract(&zip_header, src_fd, 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(src_fd, 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("cannot read input");
+                       }
+                       free(dst_fn);
+                       dst_fn = xstrdup(key_buf);
+                       chomp(dst_fn);
+                       goto _check_file;
+
+               default:
+                       printf("error: invalid response [%c]\n",(char)i);
+                       goto _check_file;
+               }
+
+               /* Data descriptor section */
+               if (zip_header.formatted.flags & 4) {
+                       /* skip over duplicate crc, compressed size and uncompressed size */
+                       unzip_skip(src_fd, 12);
+               }
+       }
+
+       if (listing && verbose) {
+               printf(" --------                   -------\n"
+                      "%9d                   %d files\n",
+                      total_size, total_entries);
+       }
+
+       return 0;
+}
diff --git a/console-tools/Config.in b/console-tools/Config.in
new file mode 100644 (file)
index 0000000..4b7f02d
--- /dev/null
@@ -0,0 +1,111 @@
+#
+# 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 SETKEYCODES
+       bool "setkeycodes"
+       default n
+       help
+         This program loads entries into the kernel's scancode-to-keycode
+         map, allowing unusual keyboards to generate usable keycodes.
+
+config SETLOGCONS
+       bool "setlogcons"
+       default n
+       help
+         This program redirects the output console of kernel messages.
+
+endmenu
diff --git a/console-tools/Kbuild b/console-tools/Kbuild
new file mode 100644 (file)
index 0000000..cf3825e
--- /dev/null
@@ -0,0 +1,20 @@
+# 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_SETKEYCODES)      += setkeycodes.o
+lib-$(CONFIG_SETLOGCONS)       += setlogcons.o
diff --git a/console-tools/chvt.c b/console-tools/chvt.c
new file mode 100644 (file)
index 0000000..ea96d13
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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"
+
+/* From <linux/vt.h> */
+enum {
+       VT_ACTIVATE = 0x5606,   /* make vt active */
+       VT_WAITACTIVE = 0x5607  /* wait for vt active */
+};
+
+int chvt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chvt_main(int argc, char **argv)
+{
+       int fd, num;
+
+       if (argc != 2) {
+               bb_show_usage();
+       }
+
+       fd = get_console_fd();
+       num = xatou_range(argv[1], 1, 63);
+       /* double cast suppresses "cast to ptr from int of different size" */
+       xioctl(fd, VT_ACTIVATE, (void *)(ptrdiff_t)num);
+       xioctl(fd, VT_WAITACTIVE, (void *)(ptrdiff_t)num);
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/clear.c b/console-tools/clear.c
new file mode 100644 (file)
index 0000000..0d94e35
--- /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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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..1200cae
--- /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 ATTRIBUTE_UNUSED, 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(), 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..40b58f7
--- /dev/null
@@ -0,0 +1,66 @@
+/* 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.
+ *
+ */
+
+#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, char **argv)
+{
+       struct kbentry ke;
+       int i, j, fd;
+       char flags[MAX_NR_KEYMAPS];
+
+       if (argc >= 2 && argv[1][0] == '-')
+               bb_show_usage();
+
+       fd = xopen(CURRENT_VC, O_RDWR);
+
+       write(1, "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(1, flags, MAX_NR_KEYMAPS);
+
+       for (i = 0; i < MAX_NR_KEYMAPS; i++) {
+               if (flags[i] == 1) {
+                       for (j = 0; j < NR_KEYS; j++) {
+                               ke.kb_index = j;
+                               ke.kb_table = i;
+                               if (!ioctl_or_perror(fd, KDGKBENT, &ke,
+                                               "ioctl failed with %s, %s, %p",
+                                               (char *)&ke.kb_index,
+                                               (char *)&ke.kb_table,
+                                               &ke.kb_value)
+                               ) {
+                                       write(1, (void*)&ke.kb_value, 2);
+                               }
+                       }
+               }
+       }
+       close(fd);
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/kbd_mode.c b/console-tools/kbd_mode.c
new file mode 100644 (file)
index 0000000..0000ea1
--- /dev/null
@@ -0,0 +1,71 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini loadkmap implementation for busybox
+ *
+ * Copyright (C) 2007 Loïc Grenié <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 <getopt.h>
+#include "libbb.h"
+#include <linux/kd.h>
+
+int kbd_mode_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int kbd_mode_main(int argc, char **argv)
+{
+       static const char opts[] = "saku";
+
+       const char *opt = argv[1];
+       const char *p;
+       int fd;
+
+       fd = get_console_fd();
+       if (fd < 0) /* get_console_fd() already complained */
+               return EXIT_FAILURE;
+
+       if (opt == NULL) {
+               /* No arg */
+               const char *msg = "unknown";
+               int mode;
+
+               ioctl(fd, KDGKBMODE, &mode);
+               switch(mode) {
+               case K_RAW:
+                       msg = "raw (scancode)";
+                       break;
+               case K_XLATE:
+                       msg = "default (ASCII)";
+                       break;
+               case K_MEDIUMRAW:
+                       msg = "mediumraw (keycode)";
+                       break;
+               case K_UNICODE:
+                       msg = "Unicode (UTF-8)";
+                       break;
+               }
+               printf("The keyboard is in %s mode\n", msg);
+       }
+       else if (argc > 2 /* more than 1 arg */
+        || *opt != '-' /* not an option */
+        || (p = strchr(opts, opt[1])) == NULL /* not an option we expect */
+        || opt[2] != '\0' /* more than one option char */
+       ) {
+               bb_show_usage();
+               /* return EXIT_FAILURE; - not reached */
+       }
+       else {
+#if K_RAW != 0 || K_XLATE != 1 || K_MEDIUMRAW != 2 || K_UNICODE != 3
+#error kbd_mode must be changed
+#endif
+               /* The options are in the order of the various K_xxx */
+               ioctl(fd, KDSKBMODE, p - opts);
+       }
+
+       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..843f4b0
--- /dev/null
@@ -0,0 +1,181 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * loadfont.c - Eugene Crosser & Andries Brouwer
+ *
+ * Version 0.96bb
+ *
+ * Loads the console font, and possibly the corresponding screen map(s).
+ * (Adapted for busybox by Matej Vela.)
+ */
+#include "libbb.h"
+#include <sys/kd.h>
+
+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);
+       /*memset(buf, 0, 16 * 1024);*/
+       for (i = 0; i < fontsize; i++)
+               memcpy(buf + (32 * i), inbuf + (unit * i), unit);
+
+#if defined(PIO_FONTX) && !defined(__sparc__)
+       {
+               struct consolefontdesc cfd;
+
+               cfd.charcount = fontsize;
+               cfd.charheight = unit;
+               cfd.chardata = buf;
+
+               if (!ioctl_or_perror(fd, PIO_FONTX, &cfd, "PIO_FONTX ioctl failed (will try PIO_FONT)"))
+                       goto ret;                       /* success */
+       }
+#endif
+       xioctl(fd, PIO_FONT, buf);
+ ret:
+       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 loadnewfont(int fd)
+{
+       enum { INBUF_SIZE = 32*1024 + 1 };
+
+       int unit;
+       unsigned inputlth, offset;
+       /* Was on stack, but 32k is a bit too much: */
+       unsigned char *inbuf = xmalloc(INBUF_SIZE);
+
+       /*
+        * We used to look at the length of the input file
+        * with stat(); now that we accept compressed files,
+        * just read the entire file.
+        */
+       inputlth = full_read(STDIN_FILENO, inbuf, INBUF_SIZE);
+       if (inputlth < 0)
+               bb_perror_msg_and_die("error reading input font");
+       if (inputlth >= INBUF_SIZE)
+               bb_error_msg_and_die("font too large");
+
+       /* test for psf first */
+       {
+               struct psf_header psfhdr;
+               int fontsize;
+               int hastable;
+               unsigned head0, head;
+
+               if (inputlth < sizeof(struct psf_header))
+                       goto no_psf;
+
+               psfhdr = *(struct psf_header *) &inbuf[0];
+
+               if (!PSF_MAGIC_OK(psfhdr))
+                       goto no_psf;
+
+               if (psfhdr.mode > PSF_MAXMODE)
+                       bb_error_msg_and_die("unsupported psf file mode");
+               fontsize = ((psfhdr.mode & PSF_MODE512) ? 512 : 256);
+#if !defined(PIO_FONTX) || defined(__sparc__)
+               if (fontsize != 256)
+                       bb_error_msg_and_die("only fontsize 256 supported");
+#endif
+               hastable = (psfhdr.mode & PSF_MODEHASTAB);
+               unit = psfhdr.charsize;
+               head0 = sizeof(struct psf_header);
+
+               head = head0 + fontsize * unit;
+               if (head > inputlth || (!hastable && head != inputlth))
+                       bb_error_msg_and_die("input file: bad length");
+               do_loadfont(fd, inbuf + head0, unit, fontsize);
+               if (hastable)
+                       do_loadtable(fd, inbuf + head, inputlth - head, fontsize);
+               return;
+       }
+
+ no_psf:
+       /* file with three code pages? */
+       if (inputlth == 9780) {
+               offset = 40;
+               unit = 16;
+       } else {
+               /* bare font */
+               if (inputlth & 0377)
+                       bb_error_msg_and_die("bad input file size");
+               offset = 0;
+               unit = inputlth / 256;
+       }
+       do_loadfont(fd, inbuf + offset, unit, 256);
+}
+
+int loadfont_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int loadfont_main(int argc, char **argv ATTRIBUTE_UNUSED)
+{
+       int fd;
+
+       if (argc != 1)
+               bb_show_usage();
+
+       fd = xopen(CURRENT_VC, O_RDWR);
+       loadnewfont(fd);
+
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/loadkmap.c b/console-tools/loadkmap.c
new file mode 100644 (file)
index 0000000..bea5a77
--- /dev/null
@@ -0,0 +1,62 @@
+/* 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, char **argv ATTRIBUTE_UNUSED)
+{
+       struct kbentry ke;
+       int i, j, fd;
+       uint16_t ibuff[NR_KEYS];
+       char flags[MAX_NR_KEYMAPS];
+       char buff[7];
+
+       if (argc != 1)
+               bb_show_usage();
+
+       fd = xopen(CURRENT_VC, O_RDWR);
+
+       xread(0, buff, 7);
+       if (strncmp(buff, BINARY_KEYMAP_MAGIC, 7))
+               bb_error_msg_and_die("this is not a valid binary keymap");
+
+       xread(0, flags, MAX_NR_KEYMAPS);
+
+       for (i = 0; i < MAX_NR_KEYMAPS; i++) {
+               if (flags[i] == 1) {
+                       xread(0, ibuff, NR_KEYS * sizeof(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);
+       return 0;
+}
diff --git a/console-tools/openvt.c b/console-tools/openvt.c
new file mode 100644 (file)
index 0000000..39b9859
--- /dev/null
@@ -0,0 +1,36 @@
+/* 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.
+ */
+
+/* getopt not needed */
+
+#include "libbb.h"
+
+int openvt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int openvt_main(int argc, char **argv)
+{
+       char vtname[sizeof(VC_FORMAT) + 2];
+
+       if (argc < 3)
+               bb_show_usage();
+
+       /* check for illegal vt number: < 1 or > 63 */
+       sprintf(vtname, VC_FORMAT, (int)xatou_range(argv[1], 1, 63));
+
+       bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+       /* grab new one */
+       close(0);
+       xopen(vtname, O_RDWR);
+       xdup2(0, STDOUT_FILENO);
+       xdup2(0, STDERR_FILENO);
+
+       argv += 2;
+       BB_EXECVP(argv[0], argv);
+       _exit(1);
+}
diff --git a/console-tools/reset.c b/console-tools/reset.c
new file mode 100644 (file)
index 0000000..a2bf44d
--- /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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       static const char *const args[] = {
+               "stty", "sane", NULL
+       };
+
+       /* no options, no getopt */
+
+       if (isatty(0) && isatty(1)) {
+               /* See 'man 4 console_codes' for details:
+                * "ESC c"                      -- Reset
+                * "ESC ( K"            -- Select user mapping
+                * "ESC [ J"            -- Erase display
+                * "ESC [ 0 m"          -- Reset all display attributes
+                * "ESC [ ? 25 h"       -- Make cursor visible.
+                */
+               printf("\033c\033(K\033[J\033[0m\033[?25h");
+               /* 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..01b1442
--- /dev/null
@@ -0,0 +1,71 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * resize - set terminal width and height.
+ *
+ * Copyright 2006 Bernhard Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* no options, no getopt */
+#include "libbb.h"
+
+#define ESC "\033"
+
+#define old_termios (*(struct termios*)&bb_common_bufsiz1)
+
+static void
+onintr(int sig ATTRIBUTE_UNUSED)
+{
+       tcsetattr(STDERR_FILENO, TCSANOW, &old_termios);
+       exit(1);
+}
+
+int resize_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int resize_main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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..0aa1d3a
--- /dev/null
@@ -0,0 +1,46 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  setconsole.c - redirect system console output
+ *
+ *  Copyright (C) 2004,2005  Enrik Berkhan <Enrik.Berkhan@inka.de>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <getopt.h>
+#include "libbb.h"
+
+#if ENABLE_FEATURE_SETCONSOLE_LONG_OPTIONS
+static const char setconsole_longopts[] ALIGN1 =
+       "reset\0" No_argument "r"
+       ;
+#endif
+
+#define OPT_SETCONS_RESET 1
+
+int setconsole_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setconsole_main(int argc, char **argv)
+{
+       unsigned long flags;
+       const char *device = CURRENT_TTY;
+
+#if ENABLE_FEATURE_SETCONSOLE_LONG_OPTIONS
+       applet_long_options = setconsole_longopts;
+#endif
+       flags = getopt32(argv, "r");
+
+       if (argc - optind > 1)
+               bb_show_usage();
+
+       if (argc - optind == 1) {
+               if (flags & OPT_SETCONS_RESET)
+                       bb_show_usage();
+               device = argv[optind];
+       } else {
+               if (flags & OPT_SETCONS_RESET)
+                       device = 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..e9a0508
--- /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 % 2 != 1 || argc < 2) {
+               bb_show_usage();
+       }
+
+       fd = get_console_fd();
+
+       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,
+                       "failed to set SCANCODE %x to KEYCODE %d",
+                       sc, a.keycode);
+               argc -= 2;
+               argv += 2;
+       }
+       return EXIT_SUCCESS;
+}
diff --git a/console-tools/setlogcons.c b/console-tools/setlogcons.c
new file mode 100644 (file)
index 0000000..b312fa7
--- /dev/null
@@ -0,0 +1,31 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * setlogcons: Send kernel messages to the current console or to console N
+ *
+ * Copyright (C) 2006 by Jan Kiszka <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 ATTRIBUTE_UNUSED, char **argv)
+{
+       struct {
+               char fn;
+               char subarg;
+       } arg;
+
+       arg.fn = 11;    /* redirect kernel messages */
+       arg.subarg = 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 0;
+}
diff --git a/coreutils/Config.in b/coreutils/Config.in
new file mode 100644 (file)
index 0000000..8d61925
--- /dev/null
@@ -0,0 +1,811 @@
+#
+# 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_INODE
+       bool "Enable -i (inode information)"
+       default n
+       depends on DF
+       help
+         This option enables support for df -i.
+
+config DIRNAME
+       bool "dirname"
+       default n
+       help
+         dirname is used to strip a non-directory suffix from
+         a file name.
+
+config DOS2UNIX
+       bool "dos2unix/unix2dos"
+       default n
+       help
+         dos2unix is used to convert a text file from DOS format to
+         UNIX format, and vice versa.
+
+config UNIX2DOS
+       bool
+       default y
+       depends on DOS2UNIX
+       help
+         unix2dos is used to convert a text file from UNIX format to
+         DOS format, and vice versa.
+
+config DU
+       bool "du (default blocksize of 512 bytes)"
+       default n
+       help
+         du is used to report the amount of disk space used
+         for specified files.
+
+config FEATURE_DU_DEFAULT_BLOCKSIZE_1K
+       bool "Use a default blocksize of 1024 bytes (1K)"
+       default y
+       depends on DU
+       help
+         Use a blocksize of (1K) instead of the default 512b.
+
+config ECHO
+       bool "echo (basic SuSv3 version taking no options)"
+       default n
+       help
+         echo is used to print a specified string to stdout.
+
+# this entry also appears in shell/Config.in, next to the echo builtin
+config FEATURE_FANCY_ECHO
+       bool "Enable echo options (-n and -e)"
+       default y
+       depends on ECHO
+       help
+         This adds options (-n and -e) to echo.
+
+config ENV
+       bool "env"
+       default n
+       help
+         env is used to set an environment variable and run
+         a command; without options it displays the current
+         environment.
+
+config FEATURE_ENV_LONG_OPTIONS
+       bool "Enable long options"
+       default n
+       depends on ENV && GETOPT_LONG
+       help
+         Support long options for the env applet.
+
+config 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 SLEEP
+       bool "sleep (single integer arg with no suffix)"
+       default n
+       help
+         sleep is used to pause for a specified number of seconds,
+
+config FEATURE_FANCY_SLEEP
+       bool "Enable multiple integer args and optional time suffixes"
+       default n
+       depends on SLEEP
+       help
+         Allow sleep to pause for specified minutes, hours, and days.
+
+config SORT
+       bool "sort"
+       default n
+       help
+         sort is used to sort lines of text in specified files.
+
+config FEATURE_SORT_BIG
+       bool "Full SuSv3 compliant sort (support -ktcsbdfiozgM)"
+       default y
+       depends on SORT
+       help
+         Without this, sort only supports  -r, -u, and an integer version
+         of -n.  Selecting this adds sort keys, floating point support, and
+         more.  This adds a little over 3k to a nonstatic build on x86.
+
+         The SuSv3 sort standard is available at:
+         http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html
+
+config 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
+       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 count variables
+
+config WHO
+       bool "who"
+       default n
+       select FEATURE_UTMP
+       help
+         who is used to show who is logged on.
+
+config WHOAMI
+       bool "whoami"
+       default n
+       help
+         whoami is used to print the username of the current
+         user id (same as id -un).
+
+config YES
+       bool "yes"
+       default n
+       help
+         yes is used to repeatedly output a specific string, or
+         the default string `y'.
+
+comment "Common options for cp and mv"
+       depends on CP || MV
+
+config FEATURE_PRESERVE_HARDLINKS
+       bool "Preserve hard links"
+       default n
+       depends on CP || MV
+       help
+         Allow cp and mv to preserve hard links.
+
+comment "Common options for ls, more and telnet"
+       depends on LS || MORE || TELNET
+
+config FEATURE_AUTOWIDTH
+       bool "Calculate terminal & column widths"
+       default y
+       depends on LS || MORE || TELNET
+       help
+         This option allows utilities such as 'ls', 'more' and 'telnet'
+         to determine the width of the screen, which can allow them to
+         display additional text or avoid wrapping text onto the next line.
+         If you leave this disabled, your utilities will be especially
+         primitive and will be unable to determine the current screen width.
+
+comment "Common options for df, du, ls"
+       depends on DF || DU || LS
+
+config FEATURE_HUMAN_READABLE
+       bool "Support for human readable output (example 13k, 23M, 235G)"
+       default n
+       depends on DF || DU || LS
+       help
+         Allow df, du, and ls to have human readable output.
+
+comment "Common options for md5sum, sha1sum"
+       depends on MD5SUM || SHA1SUM
+
+config FEATURE_MD5_SHA1_SUM_CHECK
+       bool "Enable -c, -s and -w options"
+       default n
+       depends on MD5SUM || SHA1SUM
+       help
+         Enabling the -c options allows files to be checked
+         against pre-calculated hash values.
+
+         -s and -w are useful options when verifying checksums.
+
+endmenu
diff --git a/coreutils/Kbuild b/coreutils/Kbuild
new file mode 100644 (file)
index 0000000..b9ed0d7
--- /dev/null
@@ -0,0 +1,88 @@
+# 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_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_MD5SUM)    += md5_sha1_sum.o
+lib-$(CONFIG_MKDIR)     += mkdir.o
+lib-$(CONFIG_MKFIFO)    += mkfifo.o
+lib-$(CONFIG_MKNOD)     += mknod.o
+lib-$(CONFIG_MV)        += mv.o
+lib-$(CONFIG_NICE)      += nice.o
+lib-$(CONFIG_NOHUP)     += nohup.o
+lib-$(CONFIG_OD)        += od.o
+lib-$(CONFIG_PRINTENV)  += printenv.o
+lib-$(CONFIG_PRINTF)    += printf.o
+lib-$(CONFIG_PWD)       += pwd.o
+lib-$(CONFIG_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_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
+lib-$(CONFIG_ASH)       += test.o # used by ash
+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..d536a1b
--- /dev/null
@@ -0,0 +1,51 @@
+/* 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);
+
+       if (*++argv) {
+               n = strlen(*argv);
+               m = strlen(s);
+               if ((m > n) && ((strcmp)(s+m-n, *argv) == 0)) {
+                       s[m-n] = '\0';
+               }
+       }
+
+       puts(s);
+
+       return fflush(stdout);
+}
diff --git a/coreutils/cal.c b/coreutils/cal.c
new file mode 100644 (file)
index 0000000..8a08a9a
--- /dev/null
@@ -0,0 +1,347 @@
+/* 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
+};
+
+static unsigned julian;
+
+/* 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");
+       julian = flags & 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)) {
+                       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(0);
+}
+
+/*
+ * day_array --
+ *     Fill in an array of 42 integers with a calendar.  Assume for a moment
+ *     that you took the (maximum) 6 rows in a calendar and stretched them
+ *     out end to end.  You would have 42 numbers or spaces.  This routine
+ *     builds that array for any month from Jan. 1 through Dec. 9999.
+ */
+static void day_array(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..989147b
--- /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 ATTRIBUTE_UNUSED, 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..b87740e
--- /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 ATTRIBUTE_UNUSED, 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..1bd0bd5
--- /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 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 ATTRIBUTE_UNUSED, 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..eaaefaf
--- /dev/null
@@ -0,0 +1,171 @@
+/* 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 (option_mask32 & 1)
+#define OPT_NODEREF (option_mask32 & 2)
+#define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 0x04) SKIP_DESKTOP(0))
+#define OPT_CHANGED (USE_DESKTOP(option_mask32 & 0x08) SKIP_DESKTOP(0))
+#define OPT_QUIET   (USE_DESKTOP(option_mask32 & 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(option_mask32 & BIT_TRAVERSE) SKIP_DESKTOP(0))
+/* -H or -L */
+#define BIT_TRAVERSE_TOP (0x20|0x40)
+#define OPT_TRAVERSE_TOP (USE_DESKTOP(option_mask32 & BIT_TRAVERSE_TOP) SKIP_DESKTOP(0))
+
+typedef int (*chown_fptr)(const char *, uid_t, gid_t);
+
+static struct bb_uidgid_t ugid = { -1, -1 };
+
+static int fileAction(const char *fileName, struct stat *statbuf,
+               void *cf, int depth ATTRIBUTE_UNUSED)
+{
+       uid_t u = (ugid.uid == (uid_t)-1) ? statbuf->st_uid : ugid.uid;
+       gid_t g = (ugid.gid == (gid_t)-1) ? statbuf->st_gid : ugid.gid;
+
+       if (!((chown_fptr)cf)(fileName, u, g)) {
+               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;
+}
+
+int chown_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chown_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int retval = EXIT_SUCCESS;
+       int flags;
+       chown_fptr chown_func;
+
+       opt_complementary = "-2";
+       getopt32(argv, OPT_STR);
+       argv += optind;
+
+       /* This matches coreutils behavior (almost - see below) */
+       chown_func = chown;
+       if (OPT_NODEREF
+           /* || (OPT_RECURSE && !OPT_TRAVERSE_TOP): */
+           USE_DESKTOP( || (option_mask32 & (BIT_RECURSE|BIT_TRAVERSE_TOP)) == BIT_RECURSE)
+       ) {
+               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(&ugid, argv[0]);
+
+       /* Ok, ready to do the deed now */
+       argv++;
+       do {
+               if (!recursive_action(*argv,
+                               flags,          /* flags */
+                               fileAction,     /* file action */
+                               fileAction,     /* dir action */
+                               chown_func,     /* 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..6512ccc
--- /dev/null
@@ -0,0 +1,56 @@
+/* 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       uint32_t *crc32_table = crc32_filltable(NULL, 1);
+       uint32_t crc;
+       long length, filesize;
+       int bytes_read;
+       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)
+                       continue;
+               crc = 0;
+               length = 0;
+
+#define read_buf bb_common_bufsiz1
+               while ((bytes_read = safe_read(fd, read_buf, sizeof(read_buf))) > 0) {
+                       cp = read_buf;
+                       length += bytes_read;
+                       do {
+                               crc = (crc << 8) ^ crc32_table[(crc >> 24) ^ *cp++];
+                       } while (--bytes_read);
+               }
+               close(fd);
+
+               filesize = length;
+
+               for (; length; length >>= 8)
+                       crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ length) & 0xff];
+               crc ^= 0xffffffffL;
+
+               printf((*argv ? "%" PRIu32 " %li %s\n" : "%" PRIu32 " %li\n"),
+                               crc, filesize, *argv);
+       } while (*argv && *++argv);
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/coreutils/comm.c b/coreutils/comm.c
new file mode 100644 (file)
index 0000000..4dbc0d4
--- /dev/null
@@ -0,0 +1,113 @@
+/* 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)
+{
+       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');
+       }
+       fputs(line, stdout);
+}
+
+int comm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int comm_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+#define LINE_LEN 100
+#define BB_EOF_0 0x1
+#define BB_EOF_1 0x2
+       char thisline[2][LINE_LEN];
+       FILE *streams[2];
+       int i;
+       unsigned flags;
+
+       opt_complementary = "=2";
+       flags = getopt32(argv, "123");
+       argv += optind;
+
+       for (i = 0; i < 2; ++i) {
+               streams[i] = (argv[i][0] == '-' && !argv[i][1]) ? stdin : xfopen(argv[i], "r");
+               fgets(thisline[i], LINE_LEN, streams[i]);
+       }
+
+       /* This is the real core of the program - lines are compared here */
+
+       while (*thisline[0] || *thisline[1]) {
+               int order = 0;
+
+               i = 0;
+               if (feof(streams[0])) i |= BB_EOF_0;
+               if (feof(streams[1])) i |= BB_EOF_1;
+
+               if (!*thisline[0])
+                       order = 1;
+               else if (!*thisline[1])
+                       order = -1;
+               else {
+                       int tl0_len, tl1_len;
+                       tl0_len = strlen(thisline[0]);
+                       tl1_len = strlen(thisline[1]);
+                       order = memcmp(thisline[0], thisline[1], tl0_len < tl1_len ? tl0_len : tl1_len);
+                       if (!order)
+                               order = tl0_len < tl1_len ? -1 : tl0_len != tl1_len;
+               }
+
+               if (order == 0 && !i)
+                       writeline(thisline[1], 2, flags);
+               else if (order > 0 && !(i & BB_EOF_1))
+                       writeline(thisline[1], 1, flags);
+               else if (order < 0 && !(i & BB_EOF_0))
+                       writeline(thisline[0], 0, flags);
+
+               if (i & BB_EOF_0 & BB_EOF_1) {
+                       break;
+
+               } else if (i) {
+                       i = (i & BB_EOF_0 ? 1 : 0);
+                       while (!feof(streams[i])) {
+                               if ((order < 0 && i) || (order > 0 && !i))
+                                       writeline(thisline[i], i, flags);
+                               fgets(thisline[i], LINE_LEN, streams[i]);
+                       }
+                       break;
+
+               } else {
+                       if (order >= 0)
+                               fgets(thisline[1], LINE_LEN, streams[1]);
+                       if (order <= 0)
+                               fgets(thisline[0], LINE_LEN, streams[0]);
+               }
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               fclose(streams[0]);
+               fclose(streams[1]);
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/cp.c b/coreutils/cp.c
new file mode 100644 (file)
index 0000000..6cf1e21
--- /dev/null
@@ -0,0 +1,105 @@
+/* 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),
+               OPT_L = 1 << (sizeof(FILEUTILS_CP_OPTSTR)+3),
+       };
+
+       // Need at least two arguments
+       // Soft- and hardlinking don't mix
+       // -P and -d are the same (-P is POSIX, -d is GNU)
+       // -r and -R are the same
+       // -R (and therefore -r) switches on -d (coreutils does this)
+       // -a = -pdR
+       opt_complementary = "-2:l--s:s--l:Pd:rRd:Rd:apdR";
+       flags = getopt32(argv, FILEUTILS_CP_OPTSTR "arPHL");
+       argc -= optind;
+       argv += optind;
+       flags ^= FILEUTILS_DEREFERENCE;         /* The sense of this flag was reversed. */
+       /* Default behavior of cp is to dereference, so we don't have to do
+        * anything special when we are given -L.
+        * The behavior of -H is *almost* like -L, but not quite, so let's
+        * just ignore it too for fun.
+       if (flags & OPT_L) ...
+       if (flags & OPT_H) ... // deref command-line params only
+       */
+
+#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..e617ef2
--- /dev/null
@@ -0,0 +1,279 @@
+/* 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 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
+};
+
+/* growable array holding a series of lists */
+static struct cut_list *cut_lists;
+static unsigned int nlists;    /* number of elements in above list */
+
+
+static int cmpfunc(const void *a, const void *b)
+{
+       return (((struct cut_list *) a)->startpos -
+                       ((struct cut_list *) b)->startpos);
+
+}
+
+static void cut_file(FILE *file, char delim)
+{
+       char *line = NULL;
+       unsigned int linenum = 0;       /* keep these zero-based to be consistent */
+
+       /* go through every line in the file */
+       while ((line = xmalloc_getline(file)) != NULL) {
+
+               /* set up a list so we can keep track of what's been printed */
+               char * printed = xzalloc(strlen(line) * sizeof(char));
+               char * orig_line = line;
+               unsigned int cl_pos = 0;
+               int spos;
+
+               /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
+               if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) {
+                       /* print the chars specified in each cut list */
+                       for (; cl_pos < nlists; cl_pos++) {
+                               spos = cut_lists[cl_pos].startpos;
+                               while (spos < strlen(line)) {
+                                       if (!printed[spos]) {
+                                               printed[spos] = 'X';
+                                               putchar(line[spos]);
+                                       }
+                                       spos++;
+                                       if (spos > cut_lists[cl_pos].endpos
+                                               || cut_lists[cl_pos].endpos == NON_RANGE)
+                                               break;
+                               }
+                       }
+               } else if (delim == '\n') {     /* cut by lines */
+                       spos = cut_lists[cl_pos].startpos;
+
+                       /* get out if we have no more lists to process or if the lines
+                        * are lower than what we're interested in */
+                       if (linenum < spos || cl_pos >= nlists)
+                               goto next_line;
+
+                       /* if the line we're looking for is lower than the one we were
+                        * passed, it means we displayed it already, so move on */
+                       while (spos < linenum) {
+                               spos++;
+                               /* go to the next list if we're at the end of this one */
+                               if (spos > cut_lists[cl_pos].endpos
+                                       || cut_lists[cl_pos].endpos == NON_RANGE) {
+                                       cl_pos++;
+                                       /* get out if there's no more lists to process */
+                                       if (cl_pos >= nlists)
+                                               goto next_line;
+                                       spos = cut_lists[cl_pos].startpos;
+                                       /* get out if the current line is lower than the one
+                                        * we just became interested in */
+                                       if (linenum < spos)
+                                               goto next_line;
+                               }
+                       }
+
+                       /* If we made it here, it means we've found the line we're
+                        * looking for, so print it */
+                       puts(line);
+                       goto next_line;
+               } else {                /* cut by fields */
+                       int ndelim = -1;        /* zero-based / one-based problem */
+                       int nfields_printed = 0;
+                       char *field = NULL;
+                       const char delimiter[2] = { delim, 0 };
+
+                       /* does this line contain any delimiters? */
+                       if (strchr(line, delim) == NULL) {
+                               if (!(option_mask32 & CUT_OPT_SUPPRESS_FLGS))
+                                       puts(line);
+                               goto next_line;
+                       }
+
+                       /* process each list on this line, for as long as we've got
+                        * a line to process */
+                       for (; cl_pos < nlists && line; cl_pos++) {
+                               spos = cut_lists[cl_pos].startpos;
+                               do {
+                                       /* find the field we're looking for */
+                                       while (line && ndelim < spos) {
+                                               field = strsep(&line, delimiter);
+                                               ndelim++;
+                                       }
+
+                                       /* we found it, and it hasn't been printed yet */
+                                       if (field && ndelim == spos && !printed[ndelim]) {
+                                               /* if this isn't our first time through, we need to
+                                                * print the delimiter after the last field that was
+                                                * printed */
+                                               if (nfields_printed > 0)
+                                                       putchar(delim);
+                                               fputs(field, stdout);
+                                               printed[ndelim] = 'X';
+                                               nfields_printed++;      /* shouldn't overflow.. */
+                                       }
+
+                                       spos++;
+
+                                       /* keep going as long as we have a line to work with,
+                                        * this is a list, and we're not at the end of that
+                                        * list */
+                               } while (spos <= cut_lists[cl_pos].endpos && line
+                                                && cut_lists[cl_pos].endpos != NON_RANGE);
+                       }
+               }
+               /* if we printed anything at all, we need to finish it with a
+                * newline cuz we were handed a chomped line */
+               putchar('\n');
+ next_line:
+               linenum++;
+               free(printed);
+               free(orig_line);
+       }
+}
+
+int cut_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cut_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       char delim = '\t';      /* delimiter, default is tab */
+       char *sopt, *ltok;
+
+       opt_complementary = "b--bcf:c--bcf:f--bcf";
+       getopt32(argv, optstring, &sopt, &sopt, &sopt, &ltok);
+//     argc -= optind;
+       argv += optind;
+       if (!(option_mask32 & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS)))
+               bb_error_msg_and_die("expected a list of bytes, characters, or fields");
+
+       if (option_mask32 & 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 (!(option_mask32 & CUT_OPT_FIELDS_FLGS)) {
+               static const char _op_on_field[] ALIGN1 = " only when operating on fields";
+
+               if (option_mask32 & CUT_OPT_SUPPRESS_FLGS) {
+                       bb_error_msg_and_die
+                               ("suppressing non-delimited lines makes sense%s",
+                                _op_on_field);
+               }
+               if (delim != '\t') {
+                       bb_error_msg_and_die
+                               ("a delimiter may be specified%s", _op_on_field);
+               }
+       }
+
+       /*
+        * parse list and put values into startpos and endpos.
+        * valid list formats: N, N-, N-M, -M
+        * more than one list can be separated by commas
+        */
+       {
+               char *ntok;
+               int s = 0, e = 0;
+
+               /* take apart the lists, one by one (they are separated with commas) */
+               while ((ltok = strsep(&sopt, ",")) != NULL) {
+
+                       /* it's actually legal to pass an empty list */
+                       if (!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(cut_lists, sizeof(struct cut_list) * (++nlists));
+                       cut_lists[nlists-1].startpos = s;
+                       cut_lists[nlists-1].endpos = e;
+               }
+
+               /* make sure we got some cut positions out of all that */
+               if (nlists == 0)
+                       bb_error_msg_and_die("missing list of positions");
+
+               /* now that the lists are parsed, we need to sort them to make life
+                * easier on us when it comes time to print the chars / fields / lines
+                */
+               qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc);
+       }
+
+       {
+               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);
+                       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..a8e3393
--- /dev/null
@@ -0,0 +1,237 @@
+/* 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 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, char **argv)
+{
+       time_t tm;
+       struct tm tm_time;
+       unsigned opt;
+       int ifmt = -1;
+       char *date_str = NULL;
+       char *date_fmt = NULL;
+       char *filename = NULL;
+       char *isofmt_arg;
+       char *hintfmt_arg;
+
+       opt_complementary = "d--s:s--d"
+               USE_FEATURE_DATE_ISOFMT(":R--I:I--R");
+       opt = getopt32(argv, "Rs:ud:r:"
+                       USE_FEATURE_DATE_ISOFMT("I::D:"),
+                       &date_str, &date_str, &filename
+                       USE_FEATURE_DATE_ISOFMT(, &isofmt_arg, &hintfmt_arg));
+       maybe_set_utc(opt);
+
+       if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) {
+               if (!isofmt_arg) {
+                       ifmt = 0; /* default is date */
+               } else {
+                       static const char *const isoformats[] = {
+                               "date", "hours", "minutes", "seconds"
+                       };
+
+                       for (ifmt = 0; ifmt < 4; ifmt++)
+                               if (!strcmp(isofmt_arg, isoformats[ifmt]))
+                                       goto found;
+                       /* parse error */
+                       bb_show_usage();
+ found: ;
+               }
+       }
+
+       /* XXX, date_fmt == NULL from this always */
+       if ((date_fmt == NULL) && (optind < argc) && (argv[optind][0] == '+')) {
+               date_fmt = &argv[optind][1];    /* Skip over the '+' */
+       } else if (date_str == NULL) {
+               opt |= DATE_OPT_SET;
+               date_str = argv[optind];
+       }
+
+       /* Now we have parsed all the information except the date format
+          which depends on whether the clock is being set or read */
+
+       if (filename) {
+               struct stat statbuf;
+               xstat(filename, &statbuf);
+               tm = statbuf.st_mtime;
+       } else
+               time(&tm);
+       memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
+       /* Zero out fields - take her back to midnight! */
+       if (date_str != NULL) {
+               tm_time.tm_sec = 0;
+               tm_time.tm_min = 0;
+               tm_time.tm_hour = 0;
+
+               /* Process any date input to UNIX time since 1 Jan 1970 */
+               if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) {
+                       strptime(date_str, hintfmt_arg, &tm_time);
+               } else if (strchr(date_str, ':') != NULL) {
+                       /* Parse input and assign appropriately to tm_time */
+
+                       if (sscanf(date_str, "%d:%d:%d", &tm_time.tm_hour, &tm_time.tm_min,
+                                                                &tm_time.tm_sec) == 3) {
+                               /* no adjustments needed */
+                       } else if (sscanf(date_str, "%d:%d", &tm_time.tm_hour,
+                                                                               &tm_time.tm_min) == 2) {
+                               /* no adjustments needed */
+                       } else if (sscanf(date_str, "%d.%d-%d:%d:%d", &tm_time.tm_mon,
+                                                               &tm_time.tm_mday, &tm_time.tm_hour,
+                                                               &tm_time.tm_min, &tm_time.tm_sec) == 5) {
+                               /* Adjust dates from 1-12 to 0-11 */
+                               tm_time.tm_mon -= 1;
+                       } else if (sscanf(date_str, "%d.%d-%d:%d", &tm_time.tm_mon,
+                                                               &tm_time.tm_mday,
+                                                               &tm_time.tm_hour, &tm_time.tm_min) == 4) {
+                               /* Adjust dates from 1-12 to 0-11 */
+                               tm_time.tm_mon -= 1;
+                       } else if (sscanf(date_str, "%d.%d.%d-%d:%d:%d", &tm_time.tm_year,
+                                                               &tm_time.tm_mon, &tm_time.tm_mday,
+                                                               &tm_time.tm_hour, &tm_time.tm_min,
+                                                                       &tm_time.tm_sec) == 6) {
+                               tm_time.tm_year -= 1900;        /* Adjust years */
+                               tm_time.tm_mon -= 1;    /* Adjust dates from 1-12 to 0-11 */
+                       } else if (sscanf(date_str, "%d.%d.%d-%d:%d", &tm_time.tm_year,
+                                                               &tm_time.tm_mon, &tm_time.tm_mday,
+                                                               &tm_time.tm_hour, &tm_time.tm_min) == 5) {
+                               tm_time.tm_year -= 1900;        /* Adjust years */
+                               tm_time.tm_mon -= 1;    /* Adjust dates from 1-12 to 0-11 */
+                       } else {
+                               bb_error_msg_and_die(bb_msg_invalid_date, date_str);
+                       }
+               } else {
+                       int nr;
+                       char *cp;
+
+                       nr = sscanf(date_str, "%2d%2d%2d%2d%d", &tm_time.tm_mon,
+                                               &tm_time.tm_mday, &tm_time.tm_hour, &tm_time.tm_min,
+                                               &tm_time.tm_year);
+
+                       if (nr < 4 || nr > 5) {
+                               bb_error_msg_and_die(bb_msg_invalid_date, date_str);
+                       }
+
+                       cp = strchr(date_str, '.');
+                       if (cp) {
+                               nr = sscanf(cp + 1, "%2d", &tm_time.tm_sec);
+                               if (nr != 1) {
+                                       bb_error_msg_and_die(bb_msg_invalid_date, date_str);
+                               }
+                       }
+
+                       /* correct for century  - minor Y2K problem here? */
+                       if (tm_time.tm_year >= 1900) {
+                               tm_time.tm_year -= 1900;
+                       }
+                       /* adjust date */
+                       tm_time.tm_mon -= 1;
+               }
+
+               /* Correct any day of week and day of year etc. fields */
+               tm_time.tm_isdst = -1;  /* Be sure to recheck dst. */
+               tm = mktime(&tm_time);
+               if (tm < 0) {
+                       bb_error_msg_and_die(bb_msg_invalid_date, date_str);
+               }
+               maybe_set_utc(opt);
+
+               /* if setting time, set it */
+               if ((opt & DATE_OPT_SET) && stime(&tm) < 0) {
+                       bb_perror_msg("cannot set date");
+               }
+       }
+
+       /* Display output */
+
+       /* Deal with format string */
+
+       if (date_fmt == NULL) {
+               int i;
+               date_fmt = xzalloc(32);
+               if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
+                       strcpy(date_fmt, "%Y-%m-%d");
+                       if (ifmt > 0) {
+                               i = 8;
+                               date_fmt[i++] = 'T';
+                               date_fmt[i++] = '%';
+                               date_fmt[i++] = 'H';
+                               if (ifmt > 1) {
+                                       date_fmt[i++] = ':';
+                                       date_fmt[i++] = '%';
+                                       date_fmt[i++] = 'M';
+                               }
+                               if (ifmt > 2) {
+                                       date_fmt[i++] = ':';
+                                       date_fmt[i++] = '%';
+                                       date_fmt[i++] = 'S';
+                               }
+ format_utc:
+                               date_fmt[i++] = '%';
+                               date_fmt[i] = (opt & DATE_OPT_UTC) ? 'Z' : 'z';
+                       }
+               } else if (opt & DATE_OPT_RFC2822) {
+                       /* Undo busybox.c for date -R */
+                       if (ENABLE_LOCALE_SUPPORT)
+                               setlocale(LC_TIME, "C");
+                       strcpy(date_fmt, "%a, %d %b %Y %H:%M:%S ");
+                       i = 22;
+                       goto format_utc;
+               } else /* default case */
+                       date_fmt = (char*)"%a %b %e %H:%M:%S %Z %Y";
+       }
+
+#define date_buf bb_common_bufsiz1
+       if (*date_fmt == '\0') {
+               /* With no format string, just print a blank line */
+               date_buf[0] = '\0';
+       } else {
+               /* Handle special conversions */
+
+               if (strncmp(date_fmt, "%f", 2) == 0) {
+                       date_fmt = (char*)"%Y.%m.%d-%H:%M:%S";
+               }
+
+               /* Generate output string */
+               strftime(date_buf, sizeof(date_buf), date_fmt, &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..f3330e6
--- /dev/null
@@ -0,0 +1,330 @@
+/* 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 <signal.h>  /* For FEATURE_DD_SIGNAL_HANDLING */
+#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 ATTRIBUTE_UNUSED 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 (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, char **argv)
+{
+       enum {
+               FLAG_SYNC    = 1 << 0,
+               FLAG_NOERROR = 1 << 1,
+               FLAG_NOTRUNC = 1 << 2,
+               FLAG_TWOBUFS = 1 << 3,
+               FLAG_COUNT   = 1 << 4,
+       };
+       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""notrunc\0""sync\0""noerror\0"
+#endif
+               ;
+       enum {
+               OP_bs = 1,
+               OP_count,
+               OP_seek,
+               OP_skip,
+               OP_if,
+               OP_of,
+#if ENABLE_FEATURE_DD_IBS_OBS
+               OP_ibs,
+               OP_obs,
+               OP_conv,
+               OP_conv_notrunc,
+               OP_conv_sync,
+               OP_conv_noerror,
+#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; n < argc; n++) {
+               smalluint key_len;
+               smalluint what;
+               char *key;
+               char *arg = argv[n];
+
+//XXX:FIXME: we reject plain "dd --" This would cost ~20 bytes, so..
+//if (*arg == '-' && *++arg == '-' && !*++arg) continue;
+               key = strstr(arg, "=");
+               if (key == NULL)
+                       bb_show_usage();
+               key_len = key - arg + 1;
+               key = xstrndup(arg, key_len);
+               what = index_in_strings(keywords, key) + 1;
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(key);
+               if (what == 0)
+                       bb_show_usage();
+               arg += key_len;
+#if ENABLE_FEATURE_DD_IBS_OBS
+               if (what == OP_ibs) {
+                       /* Must fit into positive ssize_t */
+                       ibs = xatoul_range_sfx(arg, 1, ((size_t)-1L)/2, dd_suffixes);
+                       continue;
+               }
+               if (what == OP_obs) {
+                       obs = xatoul_range_sfx(arg, 1, ((size_t)-1L)/2, dd_suffixes);
+                       continue;
+               }
+               if (what == OP_conv) {
+                       while (1) {
+                               /* find ',', replace them with NUL so we can use arg for
+                                * index_in_strings() without copying.
+                                * We rely on arg being non-null, else strchr would fault.
+                                */
+                               key = strchr(arg, ',');
+                               if (key)
+                                       *key = '\0';
+                               what = index_in_strings(keywords, arg) + 1;
+                               if (what < OP_conv_notrunc)
+                                       bb_error_msg_and_die(bb_msg_invalid_arg, arg, "conv");
+                               if (what == OP_conv_notrunc)
+                                       flags |= FLAG_NOTRUNC;
+                               if (what == OP_conv_sync)
+                                       flags |= FLAG_SYNC;
+                               if (what == OP_conv_noerror)
+                                       flags |= FLAG_NOERROR;
+                               if (!key) /* no ',' left, so this was the last specifier */
+                                       break;
+                               arg = key + 1; /* skip this keyword and ',' */
+                       }
+                       continue;
+               }
+#endif
+               if (what == OP_bs) {
+                       ibs = obs = xatoul_range_sfx(arg, 1, ((size_t)-1L)/2, dd_suffixes);
+                       continue;
+               }
+               /* These can be large: */
+               if (what == OP_count) {
+                       flags |= FLAG_COUNT;
+                       count = XATOU_SFX(arg, dd_suffixes);
+                       continue;
+               }
+               if (what == OP_seek) {
+                       seek = XATOU_SFX(arg, dd_suffixes);
+                       continue;
+               }
+               if (what == OP_skip) {
+                       skip = XATOU_SFX(arg, dd_suffixes);
+                       continue;
+               }
+               if (what == OP_if) {
+                       infile = arg;
+                       continue;
+               }
+               if (what == OP_of)
+                       outfile = arg;
+       }
+//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 > 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 (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..9cb328a
--- /dev/null
@@ -0,0 +1,175 @@
+/* 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 -- options -P and -t missing.  Also blocksize. */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/df.html */
+
+/* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
+ *
+ * Size reduction.  Removed floating point dependency.  Added error checking
+ * on output.  Output stats on 0-sized filesystems if specifically listed on
+ * the command line.  Properly round *-blocks, Used, and Available quantities.
+ */
+
+#include <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;
+#if ENABLE_FEATURE_HUMAN_READABLE
+       unsigned df_disp_hr = 1024;
+#endif
+       int status = EXIT_SUCCESS;
+       unsigned opt;
+       FILE *mount_table;
+       struct mntent *mount_entry;
+       struct statfs s;
+       /* default display is kilobytes */
+       const char *disp_units_hdr = "1k-blocks";
+
+       enum {
+               OPT_ALL = (1 << 0),
+               OPT_INODE = (ENABLE_FEATURE_HUMAN_READABLE ? (1 << 4) : (1 << 2))
+                           * ENABLE_FEATURE_DF_INODE
+       };
+
+#if ENABLE_FEATURE_HUMAN_READABLE
+       opt_complementary = "h-km:k-hm:m-hk";
+       opt = getopt32(argv, "ahmk" USE_FEATURE_DF_INODE("i"));
+       if (opt & (1 << 1)) { // -h
+               df_disp_hr = 0;
+               disp_units_hdr = "     Size";
+       }
+       if (opt & (1 << 2)) { // -m
+               df_disp_hr = 1024*1024;
+               disp_units_hdr = "1M-blocks";
+       }
+       if (opt & OPT_INODE) {
+               disp_units_hdr = "   Inodes";
+       }
+#else
+       opt = getopt32(argv, "ak" USE_FEATURE_DF_INODE("i"));
+#endif
+
+       printf("Filesystem           %-15sUsed Available Use%% Mounted on\n",
+                       disp_units_hdr);
+
+       mount_table = NULL;
+       argv += optind;
+       if (optind >= argc) {
+               mount_table = setmntent(bb_path_mtab_file, "r");
+               if (!mount_table) {
+                       bb_perror_msg_and_die(bb_path_mtab_file);
+               }
+       }
+
+       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, bb_path_mtab_file);
+                       if (!mount_entry) {
+                               bb_error_msg("%s: can't find mount point", mount_point);
+ SET_ERROR:
+                               status = EXIT_FAILURE;
+                               continue;
+                       }
+               }
+
+               device = mount_entry->mnt_fsname;
+               mount_point = mount_entry->mnt_dir;
+
+               if (statfs(mount_point, &s) != 0) {
+                       bb_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 ENABLE_FEATURE_HUMAN_READABLE
+                               if (df_disp_hr)
+                                       df_disp_hr = 1;
+#endif
+                       }
+                       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);
+                       }
+
+#ifdef WHY_IT_SHOULD_BE_HIDDEN
+                       if (strcmp(device, "rootfs") == 0) {
+                               continue;
+                       }
+#endif
+#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
+               }
+       }
+
+       fflush_stdout_and_exit(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..2db7e11
--- /dev/null
@@ -0,0 +1,95 @@
+/* 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 *name_buf = name_buf; /* for compiler */
+
+       in = stdin;
+       out = stdout;
+       if (fn != NULL) {
+               in = xfopen(fn, "r");
+               /*
+                  The file is then created with mode read/write and
+                  permissions 0666 for glibc 2.0.6 and earlier or
+                  0600 for glibc 2.0.7 and later.
+                */
+               name_buf = xasprintf("%sXXXXXX", fn);
+               i = mkstemp(name_buf);
+               if (i == -1
+                || fchmod(i, 0600) == -1
+                || !(out = fdopen(i, "w+"))
+               ) {
+                       bb_perror_nomsg_and_die();
+               }
+       }
+
+       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(name_buf);
+                       bb_perror_nomsg_and_die();
+               }
+// TODO: destroys symlinks. See how passwd handles this
+               xrename(name_buf, fn);
+               free(name_buf);
+       }
+}
+
+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..b469824
--- /dev/null
@@ -0,0 +1,240 @@
+/* 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"
+
+struct globals {
+#if ENABLE_FEATURE_HUMAN_READABLE
+       unsigned long disp_hr;
+#else
+       unsigned disp_k;
+#endif
+
+       int max_print_depth;
+       nlink_t count_hardlinks;
+
+       bool status;
+       bool one_file_system;
+       int print_files;
+       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 (G.one_file_system) {
+               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) {
+                               G.slink_depth = INT_MAX;        /* Convert -H to -L. */
+                       }
+               }
+       }
+
+       if (statbuf.st_nlink > G.count_hardlinks) {
+               /* 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;
+               }
+
+               newfile = last_char_is(filename, '/');
+               if (newfile)
+                       *newfile = '\0';
+
+               while ((entry = readdir(dir))) {
+                       char *name = entry->d_name;
+
+                       newfile = concat_subpath_file(filename, name);
+                       if (newfile == NULL)
+                               continue;
+                       ++G.du_depth;
+                       sum += du(newfile);
+                       --G.du_depth;
+                       free(newfile);
+               }
+               closedir(dir);
+       } else if (G.du_depth > G.print_files) {
+               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 ATTRIBUTE_UNUSED, char **argv)
+{
+       unsigned long total;
+       int slink_depth_save;
+       bool print_final_total;
+       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;
+       G.count_hardlinks = 1;
+
+       /* 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 & (1 << 9)) {
+               /* -h opt */
+               G.disp_hr = 0;
+       }
+       if (opt & (1 << 10)) {
+               /* -m opt */
+               G.disp_hr = 1024*1024;
+       }
+       if (opt & (1 << 2)) {
+               /* -k opt */
+               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 & (1 << 2)) {
+               /* -k opt */
+               G.disp_k = 1;
+       }
+#endif
+#endif
+       if (opt & (1 << 0)) {
+               /* -a opt */
+               G.print_files = INT_MAX;
+       }
+       if (opt & (1 << 1)) {
+               /* -H opt */
+               G.slink_depth = 1;
+       }
+       if (opt & (1 << 3)) {
+               /* -L opt */
+               G.slink_depth = INT_MAX;
+       }
+       if (opt & (1 << 4)) {
+               /* -s opt */
+               G.max_print_depth = 0;
+       }
+       G.one_file_system = opt & (1 << 5); /* -x opt */
+       if (opt & (1 << 7)) {
+               /* -l opt */
+               G.count_hardlinks = MAXINT(nlink_t);
+       }
+       print_final_total = opt & (1 << 8); /* -c opt */
+
+       /* 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);
+               G.slink_depth = slink_depth_save;
+       } while (*++argv);
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               reset_ino_dev_hashtable();
+       if (print_final_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..cc9b9e6
--- /dev/null
@@ -0,0 +1,300 @@
+/* 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 ATTRIBUTE_UNUSED, 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. */
+       if (dup2(1, 1) != 1)
+               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 (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:
+#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..f678565
--- /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
+ */
+
+#include <getopt.h> /* struct option */
+#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 ATTRIBUTE_UNUSED, char **argv)
+{
+       /* cleanenv was static - why? */
+       char *cleanenv[1];
+       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) {
+               cleanenv[0] = NULL;
+               environ = cleanenv;
+       } else {
+               while (unset_env) {
+                       unsetenv(unset_env->data);
+                       unset_env = unset_env->link;
+               }
+       }
+
+       while (*argv && (strchr(*argv, '=') != NULL)) {
+               if (putenv(*argv) < 0) {
+                       bb_perror_msg_and_die("putenv");
+               }
+               ++argv;
+       }
+
+       if (*argv) {
+               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(0);
+}
+
+/*
+ * Copyright (c) 1988, 1993, 1994
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. <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..a7ac8ea
--- /dev/null
@@ -0,0 +1,200 @@
+/* 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,
+};
+
+static void xputchar(char c)
+{
+       if (putchar(c) < 0)
+               bb_error_msg_and_die(bb_msg_write_error);
+}
+
+#if ENABLE_EXPAND
+static void expand(FILE *file, unsigned tab_size, unsigned opt)
+{
+       char *line;
+       char *ptr;
+       int convert;
+       int pos;
+
+       /* Increment tab_size by 1 locally.*/
+       tab_size++;
+
+       while ((line = xmalloc_fgets(file)) != NULL) {
+               convert = 1;
+               pos = 0;
+               ptr = line;
+               while (*line) {
+                       pos++;
+                       if (*line == '\t' && convert) {
+                               for (; pos < tab_size; pos++) {
+                                       xputchar(' ');
+                               }
+                       } else {
+                               if ((opt & OPT_INITIAL) && !isblank(*line)) {
+                                       convert = 0;
+                               }
+                               xputchar(*line);
+                       }
+                       if (pos == tab_size) {
+                               pos = 0;
+                       }
+                       line++;
+               }
+               free(ptr);
+       }
+}
+#endif
+
+#if ENABLE_UNEXPAND
+static void unexpand(FILE *file, unsigned int tab_size, unsigned opt)
+{
+       char *line;
+       char *ptr;
+       int convert;
+       int pos;
+       int i = 0;
+       int column = 0;
+
+       while ((line = xmalloc_fgets(file)) != NULL) {
+               convert = 1;
+               pos = 0;
+               ptr = line;
+               while (*line) {
+                       while ((*line == ' ' || *line == '\t') && convert) {
+                               pos += (*line == ' ') ? 1 : tab_size;
+                               line++;
+                               column++;
+                               if ((opt & OPT_ALL) && column == tab_size) {
+                                       column = 0;
+                                       goto put_tab;
+                               }
+                       }
+                       if (pos) {
+                               i = pos / tab_size;
+                               if (i) {
+                                       for (; i > 0; i--) {
+ put_tab:
+                                               xputchar('\t');
+                                       }
+                               } else {
+                                       for (i = pos % tab_size; i > 0; i--) {
+                                               xputchar(' ');
+                                       }
+                               }
+                               pos = 0;
+                       } else {
+                               if (opt & OPT_INITIAL) {
+                                       convert = 0;
+                               }
+                               if (opt & OPT_ALL) {
+                                       column++;
+                               }
+                               xputchar(*line);
+                               line++;
+                       }
+               }
+               free(ptr);
+       }
+}
+#endif
+
+int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int expand_main(int argc ATTRIBUTE_UNUSED, 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..959f520
--- /dev/null
@@ -0,0 +1,507 @@
+/* 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"
+
+/* The kinds of value we can have.  */
+enum valtype {
+       integer,
+       string
+};
+typedef enum valtype TYPE;
+
+#if ENABLE_EXPR_MATH_SUPPORT_64
+typedef int64_t arith_t;
+
+#define PF_REZ      "ll"
+#define PF_REZ_TYPE (long long)
+#define STRTOL(s, e, b) strtoll(s, e, b)
+#else
+typedef long arith_t;
+
+#define PF_REZ      "l"
+#define PF_REZ_TYPE (long)
+#define STRTOL(s, e, b) strtol(s, e, b)
+#endif
+
+/* TODO: use bb_strtol[l]? It's easier to check for errors... */
+
+/* A value is.... */
+struct valinfo {
+       TYPE type;                      /* Which kind. */
+       union {                         /* The value itself. */
+               arith_t i;
+               char *s;
+       } u;
+};
+typedef struct valinfo VALUE;
+
+/* The arguments given to the program, minus the program name.  */
+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 = xmalloc(sizeof(VALUE));
+       v->type = integer;
+       v->u.i = i;
+       return v;
+}
+
+/* Return a VALUE for S.  */
+
+static VALUE *str_value(const char *s)
+{
+       VALUE *v;
+
+       v = xmalloc(sizeof(VALUE));
+       v->type = string;
+       v->u.s = xstrdup(s);
+       return v;
+}
+
+/* Free VALUE V, including structure components.  */
+
+static void freev(VALUE * v)
+{
+       if (v->type == string)
+               free(v->u.s);
+       free(v);
+}
+
+/* Return nonzero if V is a null-string or zero-number.  */
+
+static int null(VALUE * v)
+{
+       if (v->type == integer)
+               return v->u.i == 0;
+       /* 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 nonzero if the next token matches STR exactly.
+   STR must not be NULL.  */
+
+static bool nextarg(const char *str)
+{
+       if (*G.args == NULL)
+               return 0;
+       return strcmp(*G.args, str) == 0;
+}
+
+/* The comparison operator handling functions.  */
+
+static int cmp_common(VALUE * l, VALUE * r, int op)
+{
+       int cmpval;
+
+       if (l->type == string || r->type == string) {
+               tostring(l);
+               tostring(r);
+               cmpval = strcmp(l->u.s, r->u.s);
+       } else
+               cmpval = l->u.i - r->u.i;
+       if (op == '<')
+               return cmpval < 0;
+       if (op == ('L' + 'E'))
+               return cmpval <= 0;
+       if (op == '=')
+               return cmpval == 0;
+       if (op == '!')
+               return cmpval != 0;
+       if (op == '>')
+               return cmpval > 0;
+       /* >= */
+       return cmpval >= 0;
+}
+
+/* The arithmetic operator handling functions.  */
+
+static arith_t arithmetic_common(VALUE * l, VALUE * r, int op)
+{
+       arith_t li, ri;
+
+       if (!toarith(l) || !toarith(r))
+               bb_error_msg_and_die("non-numeric argument");
+       li = l->u.i;
+       ri = r->u.i;
+       if ((op == '/' || op == '%') && ri == 0)
+               bb_error_msg_and_die("division by zero");
+       if (op == '+')
+               return li + ri;
+       else if (op == '-')
+               return li - ri;
+       else if (op == '*')
+               return li * ri;
+       else if (op == '/')
+               return li / ri;
+       else
+               return li % ri;
+}
+
+/* Do the : operator.
+   SV is the VALUE for the lhs (the string),
+   PV is the VALUE for the rhs (the pattern).  */
+
+static VALUE *docolon(VALUE * sv, VALUE * pv)
+{
+       VALUE *v;
+       regex_t re_buffer;
+       const int NMATCH = 2;
+       regmatch_t re_regs[NMATCH];
+
+       tostring(sv);
+       tostring(pv);
+
+       if (pv->u.s[0] == '^') {
+               bb_error_msg("\
+warning: unportable BRE: `%s': using `^' as the first character\n\
+of a basic regular expression is not portable; it is being ignored", pv->u.s);
+       }
+
+       memset(&re_buffer, 0, sizeof(re_buffer));
+       memset(re_regs, 0, sizeof(*re_regs));
+       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) {
+                       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);
+       }
+//FIXME: sounds like here is a bit missing: 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) {
+               if (nextarg("*"))
+                       op = '*';
+               else if (nextarg("/"))
+                       op = '/';
+               else if (nextarg("%"))
+                       op = '%';
+               else
+                       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) {
+               if (nextarg("+"))
+                       op = '+';
+               else if (nextarg("-"))
+                       op = '-';
+               else
+                       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) {
+               if (nextarg("<"))
+                       op = '<';
+               else if (nextarg("<="))
+                       op = 'L' + 'E';
+               else if (nextarg("=") || nextarg("=="))
+                       op = '=';
+               else if (nextarg("!="))
+                       op = '!';
+               else if (nextarg(">="))
+                       op = 'G' + 'E';
+               else if (nextarg(">"))
+                       op = '>';
+               else
+                       return l;
+               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, char **argv)
+{
+       VALUE *v;
+
+       if (argc == 1) {
+               bb_error_msg_and_die("too few arguments");
+       }
+
+       G.args = argv + 1;
+
+       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..5beb58a
--- /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/007904975/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 ATTRIBUTE_UNUSED argc, char ATTRIBUTE_UNUSED **argv)
+{
+       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..570f140
--- /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 (argc > 1 && argv[1][0] == '-'
+        && isdigit(argv[1][1])
+       ) {
+               --argc;
+               ++argv;
+               p = (*argv) + 1;
+               goto GET_COUNT;
+       }
+#endif
+
+       /* No size benefit in converting this to getopt32 */
+       while ((opt = getopt(argc, argv, head_opts)) > 0) {
+               switch (opt) {
+#if ENABLE_FEATURE_FANCY_HEAD
+               case 'q':
+                       header_threshhold = INT_MAX;
+                       break;
+               case 'v':
+                       header_threshhold = -1;
+                       break;
+               case 'c':
+                       count_bytes = 1;
+                       /* fall through */
+#endif
+               case 'n':
+                       p = optarg;
+#if ENABLE_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);    /* Avoid multibyte problems. */
+                               retval = EXIT_FAILURE;
+                       }
+                       die_if_ferror_stdout();
+               }
+               fmt = header_fmt_str;
+       } while (*++argv);
+
+       fflush_stdout_and_exit(retval);
+}
diff --git a/coreutils/hostid.c b/coreutils/hostid.c
new file mode 100644 (file)
index 0000000..433eccc
--- /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 ATTRIBUTE_UNUSED **argv)
+{
+       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..9afb100
--- /dev/null
@@ -0,0 +1,126 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini id implementation for busybox
+ *
+ * Copyright (C) 2000 by Randolph Chung <tausq@debian.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 _NOT_ compliant -- option -G is not currently supported. */
+/* Hacked by Tito Ragusa (C) 2004 to handle usernames of whatever length and to
+ * be more similar to GNU id.
+ * -Z option support: by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ */
+
+#include "libbb.h"
+
+#define PRINT_REAL        1
+#define NAME_NOT_NUMBER   2
+#define JUST_USER         4
+#define JUST_GROUP        8
+#if ENABLE_SELINUX
+#define JUST_CONTEXT     16
+#endif
+
+static int printf_full(unsigned int id, const char *arg, const char prefix)
+{
+       const char *fmt = "%cid=%u";
+       int status = EXIT_FAILURE;
+
+       if (arg) {
+               fmt = "%cid=%u(%s)";
+               status = EXIT_SUCCESS;
+       }
+       printf(fmt, prefix, id, arg);
+       return status;
+}
+
+int id_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int id_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       struct passwd *p;
+       uid_t uid;
+       gid_t gid;
+       unsigned long flags;
+       short status;
+#if ENABLE_SELINUX
+       security_context_t scontext;
+#endif
+       /* Don't allow -n -r -nr -ug -rug -nug -rnug */
+       /* Don't allow more than one username */
+       opt_complementary = "?1:u--g:g--u:r?ug:n?ug" USE_SELINUX(":u--Z:Z--u:g--Z:Z--g");
+       flags = getopt32(argv, "rnug" USE_SELINUX("Z"));
+
+       /* This values could be overwritten later */
+       uid = geteuid();
+       gid = getegid();
+       if (flags & PRINT_REAL) {
+               uid = getuid();
+               gid = getgid();
+       }
+
+       if (argv[optind]) {
+               p = getpwnam(argv[optind]);
+               /* xuname2uid is needed because it exits on failure */
+               uid = xuname2uid(argv[optind]);
+               gid = p->pw_gid;
+               /* in this case PRINT_REAL is the same */
+       }
+
+       if (flags & (JUST_GROUP | JUST_USER USE_SELINUX(| JUST_CONTEXT))) {
+               /* JUST_GROUP and JUST_USER are mutually exclusive */
+               if (flags & NAME_NOT_NUMBER) {
+                       /* bb_getXXXid(-1) exit on failure, puts cannot segfault */
+                       puts((flags & JUST_USER) ? bb_getpwuid(NULL, -1, uid) : bb_getgrgid(NULL, -1, gid));
+               } else {
+                       if (flags & JUST_USER) {
+                               printf("%u\n", uid);
+                       }
+                       if (flags & JUST_GROUP) {
+                               printf("%u\n", gid);
+                       }
+               }
+
+#if ENABLE_SELINUX
+               if (flags & JUST_CONTEXT) {
+                       selinux_or_die();
+                       if (argc - optind == 1) {
+                               bb_error_msg_and_die("user name can't be passed with -Z");
+                       }
+
+                       if (getcon(&scontext)) {
+                               bb_error_msg_and_die("can't get process context");
+                       }
+                       puts(scontext);
+               }
+#endif
+               /* exit */
+               fflush_stdout_and_exit(EXIT_SUCCESS);
+       }
+
+       /* Print full info like GNU id */
+       /* bb_getpwuid(0) doesn't exit on failure (returns NULL) */
+       status = printf_full(uid, bb_getpwuid(NULL, 0, uid), 'u');
+       bb_putchar(' ');
+       status |= printf_full(gid, bb_getgrgid(NULL, 0, gid), 'g');
+
+#if ENABLE_SELINUX
+       if (is_selinux_enabled()) {
+               security_context_t mysid;
+               const char *context;
+
+               context = "unknown";
+               getcon(&mysid);
+               if (mysid) {
+                       context = alloca(strlen(mysid) + 1);
+                       strcpy((char*)context, mysid);
+                       freecon(mysid);
+               }
+               printf(" context=%s", context);
+       }
+#endif
+
+       bb_putchar('\n');
+       fflush_stdout_and_exit(status);
+}
diff --git a/coreutils/install.c b/coreutils/install.c
new file mode 100644 (file)
index 0000000..4adcadb
--- /dev/null
@@ -0,0 +1,225 @@
+/* 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.
+ *
+ * TODO: -d option, need a way of recursively making directories and changing
+ *           owner/group, will probably modify bb_make_directory(...)
+ */
+
+#include <libgen.h>
+#include <getopt.h> /* struct option */
+
+#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"               No_argument       "g"
+       "mode\0"                No_argument       "m"
+       "owner\0"               No_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 flags;
+       int ret = EXIT_SUCCESS;
+       int isdir;
+#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_DIRECTORY     = 1 << 3,
+               OPT_PRESERVE_TIME = 1 << 4,
+               OPT_STRIP         = 1 << 5,
+               OPT_GROUP         = 1 << 6,
+               OPT_MODE          = 1 << 7,
+               OPT_OWNER         = 1 << 8,
+#if ENABLE_SELINUX
+               OPT_SET_SECURITY_CONTEXT = 1 << 9,
+               OPT_PRESERVE_SECURITY_CONTEXT = 1 << 10,
+#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") */
+       flags = getopt32(argv, "cvb" "dpsg:m:o:" USE_SELINUX("Z:"),
+                       &gid_str, &mode_str, &uid_str USE_SELINUX(, &scontext));
+       argc -= optind;
+       argv += optind;
+
+#if ENABLE_SELINUX
+       if (flags & (OPT_PRESERVE_SECURITY_CONTEXT|OPT_SET_SECURITY_CONTEXT)) {
+               selinux_or_die();
+               use_default_selinux_context = 0;
+               if (flags & OPT_PRESERVE_SECURITY_CONTEXT) {
+                       copy_flags |= FILEUTILS_PRESERVE_SECURITY_CONTEXT;
+               }
+               if (flags & 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 (flags & OPT_PRESERVE_TIME) {
+               copy_flags |= FILEUTILS_PRESERVE_STATUS;
+       }
+       mode = 0666;
+       if (flags & OPT_MODE)
+               bb_parse_mode(mode_str, &mode);
+       uid = (flags & OPT_OWNER) ? get_ug_id(uid_str, xuname2uid) : getuid();
+       gid = (flags & OPT_GROUP) ? get_ug_id(gid_str, xgroup2gid) : getgid();
+       if (flags & (OPT_OWNER|OPT_GROUP))
+               umask(0);
+
+       /* Create directories
+        * don't use bb_make_directory() as it can't change uid or gid
+        * perhaps bb_make_directory() should be improved.
+        */
+       if (flags & OPT_DIRECTORY) {
+               while ((arg = *argv++) != NULL) {
+                       char *slash = arg;
+                       while (1) {
+                               slash = strchr(slash + 1, '/');
+                               if (slash)
+                                       *slash = '\0';
+                               if (mkdir(arg, mode | 0111) == -1) {
+                                       if (errno != EEXIST) {
+                                               bb_perror_msg("cannot create %s", arg);
+                                               ret = EXIT_FAILURE;
+                                               break;
+                                       }
+                               } /* dir was created, chown? */
+                               else if ((flags & (OPT_OWNER|OPT_GROUP))
+                                && lchown(arg, uid, gid) == -1
+                               ) {
+                                       bb_perror_msg("cannot change ownership of %s", arg);
+                                       ret = EXIT_FAILURE;
+                                       break;
+                               }
+                               if (!slash)
+                                       break;
+                               *slash = '/';
+                       }
+               }
+               return ret;
+       }
+
+       if (argc < 2)
+               bb_show_usage();
+
+       last = argv[argc - 1];
+       argv[argc - 1] = NULL;
+       /* coreutils install resolves link in this case, don't use lstat */
+       isdir = stat(last, &statbuf) < 0 ? 0 : S_ISDIR(statbuf.st_mode);
+
+       while ((arg = *argv++) != NULL) {
+               char *dest = last;
+               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 ((flags & OPT_MODE) && chmod(dest, mode) == -1) {
+                       bb_perror_msg("cannot change permissions of %s", dest);
+                       ret = EXIT_FAILURE;
+               }
+#if ENABLE_SELINUX
+               if (use_default_selinux_context)
+                       setdefaultfilecon(dest);
+#endif
+               /* Set the user and group id */
+               if ((flags & (OPT_OWNER|OPT_GROUP))
+                && lchown(dest, uid, gid) == -1
+               ) {
+                       bb_perror_msg("cannot change ownership of %s", dest);
+                       ret = EXIT_FAILURE;
+               }
+               if (flags & 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..c7523a0
--- /dev/null
@@ -0,0 +1,19 @@
+/* vi: set sw=4 ts=4: */
+
+/* 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..c1796b3
--- /dev/null
@@ -0,0 +1,16 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#ifndef COREUTILS_H
+#define COREUTILS_H            1
+
+typedef int (*stat_func)(const char *fn, struct stat *ps);
+
+int cp_mv_stat2(const char *fn, struct stat *fn_stat, stat_func sf);
+int cp_mv_stat(const char *fn, struct stat *fn_stat);
+
+mode_t getopt_mk_fifo_nod(char **argv);
+
+#endif
diff --git a/coreutils/libcoreutils/cp_mv_stat.c b/coreutils/libcoreutils/cp_mv_stat.c
new file mode 100644 (file)
index 0000000..ff7c273
--- /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 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 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..32e55a5
--- /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 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..09fd396
--- /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 ATTRIBUTE_UNUSED **argv)
+{
+       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..9e5c6de
--- /dev/null
@@ -0,0 +1,955 @@
+/* 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.
+ */
+
+/*
+ * 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. ls of a symlink to a directory doesn't list directory contents
+ * 3. hidden files can make column width too large
+ *
+ * NON-OPTIMAL BEHAVIOUR:
+ * 1. autowidth reads directories twice
+ * 2. if you do a short directory listing without filetype characters
+ *    appended, there's no need to stat each one
+ * PORTABILITY:
+ * 1. requires lstat (BSD) - how do you do it without?
+ */
+
+#include <getopt.h>
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+enum {
+
+TERMINAL_WIDTH  = 80,           /* use 79 if terminal has linefold bug */
+COLUMN_GAP      = 2,            /* includes the file type char */
+
+/* what is the overall style of the listing */
+STYLE_COLUMNS   = 1 << 21,      /* fill columns */
+STYLE_LONG      = 2 << 21,      /* one record per line, extended info */
+STYLE_SINGLE    = 3 << 21,      /* one record per line */
+STYLE_MASK      = STYLE_SINGLE,
+
+/* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
+/* what file information will be listed */
+LIST_INO        = 1 << 0,
+LIST_BLOCKS     = 1 << 1,
+LIST_MODEBITS   = 1 << 2,
+LIST_NLINKS     = 1 << 3,
+LIST_ID_NAME    = 1 << 4,
+LIST_ID_NUMERIC = 1 << 5,
+LIST_CONTEXT    = 1 << 6,
+LIST_SIZE       = 1 << 7,
+LIST_DEV        = 1 << 8,
+LIST_DATE_TIME  = 1 << 9,
+LIST_FULLTIME   = 1 << 10,
+LIST_FILENAME   = 1 << 11,
+LIST_SYMLINK    = 1 << 12,
+LIST_FILETYPE   = 1 << 13,
+LIST_EXEC       = 1 << 14,
+LIST_MASK       = (LIST_EXEC << 1) - 1,
+
+/* what files will be displayed */
+DISP_DIRNAME    = 1 << 15,      /* 2 or more items? label directories */
+DISP_HIDDEN     = 1 << 16,      /* show filenames starting with . */
+DISP_DOT        = 1 << 17,      /* show . and .. */
+DISP_NOLIST     = 1 << 18,      /* show directory as itself, not contents */
+DISP_RECURSIVE  = 1 << 19,      /* show directory and everything below it */
+DISP_ROWS       = 1 << 20,      /* print across rows */
+DISP_MASK       = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
+
+/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
+SORT_FORWARD    = 0,            /* sort in reverse order */
+SORT_REVERSE    = 1 << 27,      /* sort in reverse order */
+
+SORT_NAME       = 0,            /* sort by file name */
+SORT_SIZE       = 1 << 28,      /* sort by file size */
+SORT_ATIME      = 2 << 28,      /* sort by last access time */
+SORT_CTIME      = 3 << 28,      /* sort by last change time */
+SORT_MTIME      = 4 << 28,      /* sort by last modification time */
+SORT_VERSION    = 5 << 28,      /* sort by version */
+SORT_EXT        = 6 << 28,      /* sort by file name extension */
+SORT_DIR        = 7 << 28,      /* sort by file or directory */
+SORT_MASK       = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
+
+/* which of the three times will be used */
+TIME_CHANGE     = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
+TIME_ACCESS     = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
+TIME_MASK       = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
+
+FOLLOW_LINKS    = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
+
+LS_DISP_HR      = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
+
+LIST_SHORT      = LIST_FILENAME,
+LIST_LONG       = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
+                  LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
+
+SPLIT_DIR       = 1,
+SPLIT_FILE      = 0,
+SPLIT_SUBDIR    = 2,
+
+};
+
+#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
+#define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
+#define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
+#define COLOR(mode)    ("\000\043\043\043\042\000\043\043"\
+                        "\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)])
+#define ATTR(mode)     ("\00\00\01\00\01\00\01\00"\
+                        "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)])
+
+/* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
+#if ENABLE_FEATURE_LS_COLOR
+static smallint show_color;
+/* long option entry used only for --color, which has no short option
+ * equivalent */
+static const char ls_color_opt[] ALIGN1 =
+       "color\0" Optional_argument "\xff" /* no short equivalent */
+       ;
+#else
+enum { show_color = 0 };
+#endif
+
+/*
+ * 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 */
+};
+typedef struct dnode dnode_t;
+
+static struct dnode **list_dir(const char *);
+static struct dnode **dnalloc(int);
+static int list_single(struct dnode *);
+
+static unsigned all_fmt;
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static unsigned tabstops = COLUMN_GAP;
+static unsigned terminal_width = TERMINAL_WIDTH;
+#else
+enum {
+       tabstops = COLUMN_GAP,
+       terminal_width = TERMINAL_WIDTH,
+};
+#endif
+
+static int status = EXIT_SUCCESS;
+
+static struct dnode *my_stat(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);
+                       status = 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);
+                       status = EXIT_FAILURE;
+                       return 0;
+               }
+       }
+
+       cur = xmalloc(sizeof(struct dnode));
+       cur->fullname = fullname;
+       cur->name = name;
+       cur->dstat = dstat;
+       USE_SELINUX(cur->sid = sid;)
+       return cur;
+}
+
+#if ENABLE_FEATURE_LS_COLOR
+static char fgcolor(mode_t mode)
+{
+       /* Check wheter the file is existing (if so, color it red!) */
+       if (errno == ENOENT)
+               return '\037';
+       if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+               return COLOR(0xF000);   /* File is executable ... */
+       return COLOR(mode);
+}
+
+static char bgcolor(mode_t mode)
+{
+       if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+               return ATTR(0xF000);    /* File is executable ... */
+       return ATTR(mode);
+}
+#endif
+
+#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
+static char append_char(mode_t mode)
+{
+       if (!(all_fmt & LIST_FILETYPE))
+               return '\0';
+       if (S_ISDIR(mode))
+               return '/';
+       if (!(all_fmt & LIST_EXEC))
+               return '\0';
+       if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+               return '*';
+       return APPCHAR(mode);
+}
+#endif
+
+#define countdirs(A, B) count_dirs((A), (B), 1)
+#define countsubdirs(A, B) count_dirs((A), (B), 0)
+static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
+{
+       int i, dirs;
+
+       if (!dn)
+               return 0;
+       dirs = 0;
+       for (i = 0; i < nfiles; i++) {
+               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 = strlen(dn[i]->name);
+                       if (column_width < len)
+                               column_width = len;
+               }
+               column_width += tabstops +
+                       USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
+                                    ((all_fmt & LIST_INO) ? 8 : 0) +
+                                    ((all_fmt & LIST_BLOCKS) ? 5 : 0);
+               ncols = (int) (terminal_width / column_width);
+       }
+
+       if (ncols > 1) {
+               nrows = nfiles / ncols;
+               if (nrows * ncols < nfiles)
+                       nrows++;                /* round up fractionals */
+       } else {
+               nrows = nfiles;
+               ncols = 1;
+       }
+
+       for (row = 0; row < nrows; row++) {
+               for (nc = 0; nc < ncols; nc++) {
+                       /* reach into the array based on the column and row */
+                       i = (nc * nrows) + row; /* assume display by column */
+                       if (all_fmt & DISP_ROWS)
+                               i = (row * ncols) + nc; /* display across row */
+                       if (i < nfiles) {
+                               if (column > 0) {
+                                       nexttab -= column;
+                                       printf("%*s", nexttab, "");
+                                       column += nexttab;
+                               }
+                               nexttab = column + column_width;
+                               column += list_single(dn[i]);
+                       }
+               }
+               putchar('\n');
+               column = 0;
+       }
+}
+
+
+static void showdirs(struct dnode **dn, int ndirs, int first)
+{
+       int i, nfiles;
+       struct dnode **subdnp;
+       int dndirs;
+       struct dnode **dnd;
+
+       if (dn == NULL || ndirs < 1)
+               return;
+
+       for (i = 0; i < ndirs; i++) {
+               if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
+                       if (!first)
+                               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) {
+               status = EXIT_FAILURE;
+               return NULL;    /* could not open the dir */
+       }
+       while ((entry = readdir(dir)) != NULL) {
+               char *fullname;
+
+               /* are we going to list the file- it may be . or .. or a hidden file */
+               if (entry->d_name[0] == '.') {
+                       if ((!entry->d_name[1] || (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;
+}
+
+
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+/* Do time() just once. Saves one syscall per file for "ls -l" */
+/* Initialized in main() */
+static time_t current_time_t;
+#endif
+
+static int list_single(struct dnode *dn)
+{
+       int i, column = 0;
+
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+       char *filetime;
+       time_t ttime, age;
+#endif
+#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
+       struct stat info;
+       char append;
+#endif
+
+       if (dn->fullname == NULL)
+               return 0;
+
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+       ttime = dn->dstat.st_mtime;     /* the default time */
+       if (all_fmt & TIME_ACCESS)
+               ttime = dn->dstat.st_atime;
+       if (all_fmt & TIME_CHANGE)
+               ttime = dn->dstat.st_ctime;
+       filetime = ctime(&ttime);
+#endif
+#if ENABLE_FEATURE_LS_FILETYPES
+       append = append_char(dn->dstat.st_mode);
+#endif
+
+       for (i = 0; i <= 31; i++) {
+               switch (all_fmt & (1 << i)) {
+               case LIST_INO:
+                       column += printf("%7ld ", (long) dn->dstat.st_ino);
+                       break;
+               case LIST_BLOCKS:
+                       column += printf("%4"OFF_FMT"d ", (off_t) dn->dstat.st_blocks >> 1);
+                       break;
+               case LIST_MODEBITS:
+                       column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
+                       break;
+               case LIST_NLINKS:
+                       column += printf("%4ld ", (long) dn->dstat.st_nlink);
+                       break;
+               case LIST_ID_NAME:
+#if ENABLE_FEATURE_LS_USERNAME
+                       printf("%-8.8s %-8.8s",
+                               get_cached_username(dn->dstat.st_uid),
+                               get_cached_groupname(dn->dstat.st_gid));
+                       column += 17;
+                       break;
+#endif
+               case LIST_ID_NUMERIC:
+                       column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid);
+                       break;
+               case LIST_SIZE:
+               case LIST_DEV:
+                       if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
+                               column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev),
+                                          (int) minor(dn->dstat.st_rdev));
+                       } else {
+                               if (all_fmt & LS_DISP_HR) {
+                                       column += printf("%9s ",
+                                               make_human_readable_str(dn->dstat.st_size, 1, 0));
+                               } else {
+                                       column += printf("%9"OFF_FMT"d ", (off_t) dn->dstat.st_size);
+                               }
+                       }
+                       break;
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+               case LIST_FULLTIME:
+                       printf("%24.24s ", filetime);
+                       column += 25;
+                       break;
+               case LIST_DATE_TIME:
+                       if ((all_fmt & LIST_FULLTIME) == 0) {
+                               /* 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;
+                       }
+                       break;
+#endif
+#if ENABLE_SELINUX
+               case LIST_CONTEXT:
+                       {
+                               char context[80];
+                               int len = 0;
+
+                               if (dn->sid) {
+                                       /* I assume sid initilized with NULL */
+                                       len = strlen(dn->sid) + 1;
+                                       safe_strncpy(context, dn->sid, len);
+                                       freecon(dn->sid);
+                               } else {
+                                       safe_strncpy(context, "unknown", 8);
+                               }
+                               printf("%-32s ", context);
+                               column += MAX(33, len);
+                       }
+                       break;
+#endif
+               case LIST_FILENAME:
+                       errno = 0;
+#if ENABLE_FEATURE_LS_COLOR
+                       if (show_color && !lstat(dn->fullname, &info)) {
+                               printf("\033[%d;%dm", bgcolor(info.st_mode),
+                                               fgcolor(info.st_mode));
+                       }
+#endif
+                       column += printf("%s", dn->name);
+                       if (show_color) {
+                               printf("\033[0m");
+                       }
+                       break;
+               case LIST_SYMLINK:
+                       if (S_ISLNK(dn->dstat.st_mode)) {
+                               char *lpath = xmalloc_readlink_or_warn(dn->fullname);
+                               if (!lpath) break;
+                               printf(" -> ");
+#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
+                               if (!stat(dn->fullname, &info)) {
+                                       append = append_char(info.st_mode);
+                               }
+#endif
+#if ENABLE_FEATURE_LS_COLOR
+                               if (show_color) {
+                                       errno = 0;
+                                       printf("\033[%d;%dm", bgcolor(info.st_mode),
+                                                  fgcolor(info.st_mode));
+                               }
+#endif
+                               column += printf("%s", lpath) + 4;
+                               if (show_color) {
+                                       printf("\033[0m");
+                               }
+                               free(lpath);
+                       }
+                       break;
+#if ENABLE_FEATURE_LS_FILETYPES
+               case LIST_FILETYPE:
+                       if (append) {
+                               putchar(append);
+                               column++;
+                       }
+                       break;
+#endif
+               }
+       }
+
+       return column;
+}
+
+/* "[-]Cadil1", POSIX mandated options, busybox always supports */
+/* "[-]gnsx", POSIX non-mandated options, busybox always supports */
+/* "[-]Ak" GNU options, busybox always supports */
+/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
+/* "[-]p", POSIX non-mandated options, busybox optionally supports */
+/* "[-]SXvThw", GNU options, busybox optionally supports */
+/* "[-]K", SELinux mandated options, busybox optionally supports */
+/* "[-]e", I think we made this one up */
+static const char ls_options[] ALIGN1 =
+       "Cadil1gnsxAk"
+       USE_FEATURE_LS_TIMESTAMPS("cetu")
+       USE_FEATURE_LS_SORTFILES("SXrv")
+       USE_FEATURE_LS_FILETYPES("Fp")
+       USE_FEATURE_LS_FOLLOWLINKS("L")
+       USE_FEATURE_LS_RECURSIVE("R")
+       USE_FEATURE_HUMAN_READABLE("h")
+       USE_SELINUX("K")
+       USE_FEATURE_AUTOWIDTH("T:w:")
+       USE_SELINUX("Z");
+
+enum {
+       LIST_MASK_TRIGGER       = 0,
+       STYLE_MASK_TRIGGER      = STYLE_MASK,
+       DISP_MASK_TRIGGER       = DISP_ROWS,
+       SORT_MASK_TRIGGER       = SORT_MASK,
+};
+
+static const unsigned opt_flags[] = {
+       LIST_SHORT | STYLE_COLUMNS, /* C */
+       DISP_HIDDEN | DISP_DOT,     /* a */
+       DISP_NOLIST,                /* d */
+       LIST_INO,                   /* i */
+       LIST_LONG | STYLE_LONG,     /* l - remember LS_DISP_HR in mask! */
+       LIST_SHORT | STYLE_SINGLE,  /* 1 */
+       0,                          /* g - ingored */
+       LIST_ID_NUMERIC,            /* n */
+       LIST_BLOCKS,                /* s */
+       DISP_ROWS,                  /* x */
+       DISP_HIDDEN,                /* A */
+       ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+       TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME),   /* c */
+       LIST_FULLTIME,              /* e */
+       ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME,   /* t */
+       TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME),   /* u */
+#endif
+#if ENABLE_FEATURE_LS_SORTFILES
+       SORT_SIZE,                  /* S */
+       SORT_EXT,                   /* X */
+       SORT_REVERSE,               /* r */
+       SORT_VERSION,               /* v */
+#endif
+#if ENABLE_FEATURE_LS_FILETYPES
+       LIST_FILETYPE | LIST_EXEC,  /* F */
+       LIST_FILETYPE,              /* p */
+#endif
+#if ENABLE_FEATURE_LS_FOLLOWLINKS
+       FOLLOW_LINKS,               /* L */
+#endif
+#if ENABLE_FEATURE_LS_RECURSIVE
+       DISP_RECURSIVE,             /* R */
+#endif
+#if ENABLE_FEATURE_HUMAN_READABLE
+       LS_DISP_HR,                 /* h */
+#endif
+#if ENABLE_SELINUX
+       LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+       0, 0,                       /* T, w - ignored */
+#endif
+#if ENABLE_SELINUX
+       LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
+#endif
+       (1U<<31)
+};
+
+
+/* THIS IS A "SAFE" APPLET, main() MAY BE CALLED INTERNALLY FROM SHELL */
+/* BE CAREFUL! */
+
+int ls_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ls_main(int argc, char **argv)
+{
+       struct dnode **dnd;
+       struct dnode **dnf;
+       struct dnode **dnp;
+       struct dnode *dn;
+       struct dnode *cur;
+       unsigned opt;
+       int nfiles = 0;
+       int dnfiles;
+       int dndirs;
+       int oi;
+       int ac;
+       int i;
+       char **av;
+       USE_FEATURE_LS_COLOR(char *color_opt;)
+
+#if ENABLE_FEATURE_LS_TIMESTAMPS
+       time(&current_time_t);
+#endif
+
+       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_color_opt;)
+#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")))
+                       show_color = 1;
+       }
+       if (opt & (1 << i)) {  /* next flag after short options */
+               if (!color_opt || !strcmp("always", color_opt))
+                       show_color = 1;
+               else if (color_opt && !strcmp("never", color_opt))
+                       show_color = 0;
+               else if (color_opt && !strcmp("auto", color_opt) && isatty(STDOUT_FILENO))
+                       show_color = 1;
+       }
+#endif
+
+       /* sort out which command line options take precedence */
+       if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
+               all_fmt &= ~DISP_RECURSIVE;     /* no recurse if listing only dir */
+       if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
+               if (all_fmt & TIME_CHANGE)
+                       all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
+               if (all_fmt & TIME_ACCESS)
+                       all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
+       }
+       if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
+               all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
+       if (ENABLE_FEATURE_LS_USERNAME)
+               if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
+                       all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
+
+       /* choose a display format */
+       if (!(all_fmt & STYLE_MASK))
+               all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
+
+       /*
+        * when there are no cmd line args we have to supply a default "." arg.
+        * we will create a second argv array, "av" that will hold either
+        * our created "." arg, or the real cmd line args.  The av array
+        * just holds the pointers- we don't move the date the pointers
+        * point to.
+        */
+       ac = argc - optind;     /* how many cmd line args are left */
+       if (ac < 1) {
+               static const char *const dotdir[] = { "." };
+
+               av = (char **) dotdir;
+               ac = 1;
+       } else {
+               av = argv + optind;
+       }
+
+       /* now, everything is in the av array */
+       if (ac > 1)
+               all_fmt |= DISP_DIRNAME;        /* 2 or more items? label directories */
+
+       /* stuff the command line file names into a dnode array */
+       dn = NULL;
+       for (oi = 0; oi < ac; oi++) {
+               /* ls w/o -l follows links on command line */
+               cur = my_stat(av[oi], av[oi], !(all_fmt & STYLE_LONG));
+               if (!cur)
+                       continue;
+               cur->allocated = 0;
+               cur->next = dn;
+               dn = cur;
+               nfiles++;
+       }
+
+       /* now that we know how many files there are
+        * allocate memory for an array to hold dnode pointers
+        */
+       dnp = dnalloc(nfiles);
+       for (i = 0, cur = dn; i < nfiles; i++) {
+               dnp[i] = cur;   /* save pointer to node in array */
+               cur = cur->next;
+       }
+
+       if (all_fmt & DISP_NOLIST) {
+               dnsort(dnp, nfiles);
+               if (nfiles > 0)
+                       showfiles(dnp, nfiles);
+       } else {
+               dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
+               dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
+               dndirs = countdirs(dnp, nfiles);
+               dnfiles = nfiles - dndirs;
+               if (dnfiles > 0) {
+                       dnsort(dnf, dnfiles);
+                       showfiles(dnf, dnfiles);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               free(dnf);
+               }
+               if (dndirs > 0) {
+                       dnsort(dnd, dndirs);
+                       showdirs(dnd, dndirs, dnfiles == 0);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               free(dnd);
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               dfree(dnp, nfiles);
+       return status;
+}
diff --git a/coreutils/md5_sha1_sum.c b/coreutils/md5_sha1_sum.c
new file mode 100644 (file)
index 0000000..080eac5
--- /dev/null
@@ -0,0 +1,175 @@
+/* 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 { HASH_SHA1, HASH_MD5 } hash_algo_t;
+
+#define FLAG_SILENT    1
+#define FLAG_CHECK     2
+#define FLAG_WARN      4
+
+/* This might be useful elsewhere */
+static unsigned char *hash_bin_to_hex(unsigned char *hash_value,
+                               unsigned hash_length)
+{
+       /* xzalloc zero-terminates */
+       char *hex_value = xzalloc((hash_length * 2) + 1);
+       bin2hex(hex_value, (char*)hash_value, hash_length);
+       return hex_value;
+}
+
+static uint8_t *hash_file(const char *filename, hash_algo_t hash_algo)
+{
+       int src_fd, hash_len, count;
+       union _ctx_ {
+               sha1_ctx_t sha1;
+               md5_ctx_t md5;
+       } context;
+       uint8_t *hash_value = NULL;
+       RESERVE_CONFIG_UBUFFER(in_buf, 4096);
+       void (*update)(const void*, size_t, void*);
+       void (*final)(void*, void*);
+
+       src_fd = 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 (*)(const void*, size_t, void*))md5_hash;
+               final = (void (*)(void*, void*))md5_end;
+               hash_len = 16;
+       } else if (ENABLE_SHA1SUM && hash_algo==HASH_SHA1) {
+               sha1_begin(&context.sha1);
+               update = (void (*)(const void*, size_t, void*))sha1_hash;
+               final = (void (*)(void*, void*))sha1_end;
+               hash_len = 20;
+       } else {
+               bb_error_msg_and_die("algorithm not supported");
+       }
+
+       while (0 < (count = safe_read(src_fd, in_buf, 4096))) {
+               update(in_buf, count, &context);
+       }
+
+       if (count == 0) {
+               final(in_buf, &context);
+               hash_value = hash_bin_to_hex(in_buf, hash_len);
+       }
+
+       RELEASE_CONFIG_BUFFER(in_buf);
+
+       if (src_fd != STDIN_FILENO) {
+               close(src_fd);
+       }
+
+       return hash_value;
+}
+
+int md5_sha1_sum_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int md5_sha1_sum_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int return_value = EXIT_SUCCESS;
+       uint8_t *hash_value;
+       unsigned flags;
+       hash_algo_t hash_algo = ENABLE_MD5SUM
+               ? (ENABLE_SHA1SUM ? (applet_name[0] == 'm' ? HASH_MD5 : HASH_SHA1) : HASH_MD5)
+               : HASH_SHA1;
+
+       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_getline(pre_computed_stream)) != NULL) {
+                       char *filename_ptr;
+
+                       count_total++;
+                       filename_ptr = strstr(line, "  ");
+                       /* handle format for binary checksums */
+                       if (filename_ptr == NULL) {
+                               filename_ptr = strstr(line, " *");
+                       }
+                       if (filename_ptr == NULL) {
+                               if (flags & FLAG_WARN) {
+                                       bb_error_msg("invalid format");
+                               }
+                               count_failed++;
+                               return_value = EXIT_FAILURE;
+                               free(line);
+                               continue;
+                       }
+                       *filename_ptr = '\0';
+                       filename_ptr += 2;
+
+                       hash_value = hash_file(filename_ptr, hash_algo);
+
+                       if (hash_value && (strcmp((char*)hash_value, line) == 0)) {
+                               if (!(flags & FLAG_SILENT))
+                                       printf("%s: OK\n", filename_ptr);
+                       } else {
+                               if (!(flags & FLAG_SILENT))
+                                       printf("%s: FAILED\n", filename_ptr);
+                               count_failed++;
+                               return_value = EXIT_FAILURE;
+                       }
+                       /* possible free(NULL) */
+                       free(hash_value);
+                       free(line);
+               }
+               if (count_failed && !(flags & FLAG_SILENT)) {
+                       bb_error_msg("WARNING: %d of %d computed checksums did NOT match",
+                                                count_failed, count_total);
+               }
+               /*
+               if (fclose_if_not_stdin(pre_computed_stream) == EOF) {
+                       bb_perror_msg_and_die("cannot close file %s", file_ptr);
+               }
+               */
+       } else {
+               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..6bdf76d
--- /dev/null
@@ -0,0 +1,81 @@
+/* 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 <getopt.h> /* struct option */
+#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..d9261b9
--- /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 ATTRIBUTE_UNUSED, 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..d8dc6c0
--- /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 <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h> /* struct option */
+#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
+
+static const char fmt[] ALIGN1 =
+       "cannot overwrite %sdirectory with %sdirectory";
+
+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 long 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)) {
+                       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_stat(*argv, &source_stat)) < 1
+                       ) {
+                               bb_perror_msg("cannot rename '%s'", *argv);
+                       } else {
+                               if (dest_exists) {
+                                       if (dest_exists == 3) {
+                                               if (source_exists != 3) {
+                                                       bb_error_msg(fmt, "", "non-");
+                                                       goto RET_1;
+                                               }
+                                       } else {
+                                               if (source_exists == 3) {
+                                                       bb_error_msg(fmt, "non-", "");
+                                                       goto RET_1;
+                                               }
+                                       }
+                                       if (unlink(dest) < 0) {
+                                               bb_perror_msg("cannot remove '%s'", dest);
+                                               goto RET_1;
+                                       }
+                               }
+                               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..7d6a51a
--- /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 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..85e979f
--- /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 <getopt.h>
+
+#include "dump.h"
+
+#define isdecdigit(c) isdigit(c)
+#define ishexdigit(c) (isxdigit)(c)
+
+static void
+odoffset(int argc, char ***argvp)
+{
+       char *num, *p;
+       int base;
+       char *end;
+
+       /*
+        * The offset syntax of od(1) was genuinely bizarre.  First, if
+        * it started with a plus it had to be an offset.  Otherwise, if
+        * there were at least two arguments, a number or lower-case 'x'
+        * followed by a number makes it an offset.  By default it was
+        * octal; if it started with 'x' or '0x' it was hex.  If it ended
+        * in a '.', it was decimal.  If a 'b' or 'B' was appended, it
+        * multiplied the number by 512 or 1024 byte units.  There was
+        * no way to assign a block count to a hex offset.
+        *
+        * We assumes it's a file if the offset is bad.
+        */
+       p = **argvp;
+
+       if (!p) {
+               /* hey someone is probably piping to us ... */
+               return;
+       }
+
+       if ((*p != '+')
+               && (argc < 2
+                       || (!isdecdigit(p[0])
+                               && ((p[0] != 'x') || !ishexdigit(p[1])))))
+               return;
+
+       base = 0;
+       /*
+        * bb_dump_skip over leading '+', 'x[0-9a-fA-f]' or '0x', and
+        * set base.
+        */
+       if (p[0] == '+')
+               ++p;
+       if (p[0] == 'x' && ishexdigit(p[1])) {
+               ++p;
+               base = 16;
+       } else if (p[0] == '0' && p[1] == 'x') {
+               p += 2;
+               base = 16;
+       }
+
+       /* bb_dump_skip over the number */
+       if (base == 16)
+               for (num = p; ishexdigit(*p); ++p);
+       else
+               for (num = p; isdecdigit(*p); ++p);
+
+       /* check for no number */
+       if (num == p)
+               return;
+
+       /* if terminates with a '.', base is decimal */
+       if (*p == '.') {
+               if (base)
+                       return;
+               base = 10;
+       }
+
+       bb_dump_skip = strtol(num, &end, base ? base : 8);
+
+       /* if end isn't the same as p, we got a non-octal digit */
+       if (end != p)
+               bb_dump_skip = 0;
+       else {
+               if (*p) {
+                       if (*p == 'b') {
+                               bb_dump_skip *= 512;
+                               ++p;
+                       } else if (*p == 'B') {
+                               bb_dump_skip *= 1024;
+                               ++p;
+                       }
+               }
+               if (*p)
+                       bb_dump_skip = 0;
+               else {
+                       ++*argvp;
+                       /*
+                        * If the offset uses a non-octal base, the base of
+                        * the offset is changed as well.  This isn't pretty,
+                        * but it's easy.
+                        */
+#define        TYPE_OFFSET     7
+                       {
+                               char x_or_d;
+                               if (base == 16) {
+                                       x_or_d = 'x';
+                                       goto DO_X_OR_D;
+                               }
+                               if (base == 10) {
+                                       x_or_d = 'd';
+                               DO_X_OR_D:
+                                       bb_dump_fshead->nextfu->fmt[TYPE_OFFSET]
+                                               = bb_dump_fshead->nextfs->nextfu->fmt[TYPE_OFFSET]
+                                               = x_or_d;
+                               }
+                       }
+               }
+       }
+}
+
+static const char *const add_strings[] = {
+       "16/1 \"%3_u \" \"\\n\"",                               /* a */
+       "8/2 \" %06o \" \"\\n\"",                               /* B, o */
+       "16/1 \"%03o \" \"\\n\"",                               /* b */
+       "16/1 \"%3_c \" \"\\n\"",                               /* c */
+       "8/2 \"  %05u \" \"\\n\"",                              /* d */
+       "4/4 \"     %010u \" \"\\n\"",                  /* D */
+       "2/8 \"          %21.14e \" \"\\n\"",   /* e (undocumented in od), F */
+       "4/4 \" %14.7e \" \"\\n\"",                             /* f */
+       "4/4 \"       %08x \" \"\\n\"",                 /* H, X */
+       "8/2 \"   %04x \" \"\\n\"",                             /* h, x */
+       "4/4 \"    %11d \" \"\\n\"",                    /* I, L, l */
+       "8/2 \" %6d \" \"\\n\"",                                /* i */
+       "4/4 \"    %011o \" \"\\n\"",                   /* O */
+};
+
+static const char od_opts[] 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;
+       bb_dump_vflag = FIRST;
+       bb_dump_length = -1;
+
+       while ((ch = getopt(argc, argv, od_opts)) > 0) {
+               if (ch == 'v') {
+                       bb_dump_vflag = ALL;
+               } else if (((p = strchr(od_opts, ch)) != NULL) && (*p != '\0')) {
+                       if (first) {
+                               first = 0;
+                               bb_dump_add("\"%07.7_Ao\n\"");
+                               bb_dump_add("\"%07.7_ao  \"");
+                       } else {
+                               bb_dump_add("\"         \"");
+                       }
+                       bb_dump_add(add_strings[(int)od_o2si[(p-od_opts)]]);
+               } else {        /* P, p, s, w, or other unhandled */
+                       bb_show_usage();
+               }
+       }
+       if (!bb_dump_fshead) {
+               bb_dump_add("\"%07.7_Ao\n\"");
+               bb_dump_add("\"%07.7_ao  \" 8/2 \"%06o \" \"\\n\"");
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       odoffset(argc, &argv);
+
+       return bb_dump_dump(argv);
+}
+#endif /* ENABLE_DESKTOP */
+
+/*-
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/coreutils/od_bloaty.c b/coreutils/od_bloaty.c
new file mode 100644 (file)
index 0000000..dce2349
--- /dev/null
@@ -0,0 +1,1432 @@
+/* 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"
+#include <getopt.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 ATTRIBUTE_UNUSED)
+{
+       /* 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 ATTRIBUTE_UNUSED)
+{
+       // buf[N] pos:  01234 56789
+       char buf[12] = "   x\0 0xx\0";
+
+       while (n_bytes--) {
+               const char *s;
+               unsigned c = *(unsigned char *) block++;
+
+               if (ISPRINT(c)) {
+                       buf[3] = c;
+                       fputs(buf, stdout);
+                       continue;
+               }
+               switch (c) {
+               case '\0':
+                       s = "  \\0";
+                       break;
+               case '\007':
+                       s = "  \\a";
+                       break;
+               case '\b':
+                       s = "  \\b";
+                       break;
+               case '\f':
+                       s = "  \\f";
+                       break;
+               case '\n':
+                       s = "  \\n";
+                       break;
+               case '\r':
+                       s = "  \\r";
+                       break;
+               case '\t':
+                       s = "  \\t";
+                       break;
+               case '\v':
+                       s = "  \\v";
+                       break;
+               case '\x7f':
+                       s = " 177";
+                       break;
+               default: /* c is never larger than 040 */
+                       buf[7] = (c >> 3) + '0';
+                       buf[8] = (c & 7) + '0';
+                       s = buf + 5;
+               }
+               fputs(s, stdout);
+       }
+}
+
+/* Given a list of one or more input filenames FILE_LIST, set the global
+   file pointer IN_STREAM and the global string INPUT_FILENAME to the
+   first one that can be successfully opened. Modify FILE_LIST to
+   reference the next filename in the list.  A file name of "-" is
+   interpreted as standard input.  If any file open fails, give an error
+   message and return nonzero.  */
+
+static void
+open_next_file(void)
+{
+       while (1) {
+               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, make *NEXT point at the
+   character following the just-decoded format (if *NEXT is non-NULL),
+   and return zero.  For example, if S were "d4afL"
+   *NEXT would be set to "afL" and *TSPEC would be
+       {
+               fmt = SIGNED_DECIMAL;
+               size = INT or LONG; (whichever integral_type_size[4] resolves to)
+               print_function = print_int; (assuming size == INT)
+               fmt_string = "%011d%c";
+       }
+   S_ORIG is solely for reporting errors.  It should be the full format
+   string argument. */
+
+static void
+decode_one_format(const char *s_orig, const char *s, const char **next,
+                                          struct tspec *tspec)
+{
+       enum size_spec size_spec;
+       unsigned size;
+       enum output_format fmt;
+       const char *p;
+       char *end;
+       char *fmt_string = NULL;
+       void (*print_function) (size_t, const char *, const char *);
+       unsigned c;
+       unsigned field_width = 0;
+       int pos;
+
+       assert(tspec != NULL);
+
+       switch (*s) {
+       case 'd':
+       case 'o':
+       case 'u':
+       case 'x': {
+               static const char CSIL[] 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[] = {
+                               sizeof(char),
+                               sizeof(short),
+                               sizeof(int),
+                               sizeof(long),
+                       };
+                       size = CSIL_sizeof[p - CSIL];
+               }
+
+#define ISPEC_TO_FORMAT(Spec, Min_format, Long_format, Max_format) \
+       ((Spec) == LONG_LONG ? (Max_format) \
+       : ((Spec) == LONG ? (Long_format) : (Min_format)))
+
+#define FMT_BYTES_ALLOCATED 9
+               size_spec = integral_type_size[size];
+
+               {
+                       static const char doux[] 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++;
+
+       if (next != NULL)
+               *next = s;
+}
+
+/* Decode the modern od format string S.  Append the decoded
+   representation to the global array SPEC, reallocating SPEC if
+   necessary.  */
+
+static void
+decode_format_string(const char *s)
+{
+       const char *s_orig = s;
+
+       while (*s != '\0') {
+               struct tspec tspec;
+               const char *next;
+
+               decode_one_format(s_orig, s, &next, &tspec);
+
+               assert(s != next);
+               s = next;
+               n_specs++;
+               spec = xrealloc(spec, n_specs * sizeof(*spec));
+               memcpy(&spec[n_specs-1], &tspec, sizeof *spec);
+       }
+}
+
+/* Given a list of one or more input filenames FILE_LIST, set the global
+   file pointer IN_STREAM to position N_SKIP in the concatenation of
+   those files.  If any file operation fails or if there are fewer than
+   N_SKIP bytes in the combined input, give an error message and return
+   nonzero.  When possible, use seek rather than read operations to
+   advance IN_STREAM.  */
+
+static void
+skip(off_t n_skip)
+{
+       if (n_skip == 0)
+               return;
+
+       while (in_stream) { /* !EOF */
+               struct stat file_stats;
+
+               /* First try seeking.  For large offsets, this extra work is
+                  worthwhile.  If the offset is below some threshold it may be
+                  more efficient to move the pointer by reading.  There are two
+                  issues when trying to seek:
+                       - the file must be seekable.
+                       - before seeking to the specified position, make sure
+                         that the new position is in the current file.
+                         Try to do that by getting file's size using fstat.
+                         But that will work only for regular files.  */
+
+                       /* The st_size field is valid only for regular files
+                          (and for symbolic links, which cannot occur here).
+                          If the number of bytes left to skip is at least
+                          as large as the size of the current file, we can
+                          decrement n_skip and go on to the next file.  */
+               if (fstat(fileno(in_stream), &file_stats) == 0
+                && S_ISREG(file_stats.st_mode) && file_stats.st_size > 0
+               ) {
+                       if (file_stats.st_size < n_skip) {
+                               n_skip -= file_stats.st_size;
+                               /* take "check & close / open_next" route */
+                       } else {
+                               if (fseeko(in_stream, n_skip, SEEK_CUR) != 0)
+                                       ioerror = 1;
+                               return;
+                       }
+               } else {
+                       /* If it's not a regular file with 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 ATTRIBUTE_UNUSED, char c ATTRIBUTE_UNUSED)
+{
+}
+
+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(lst_t->data);
+               lst_t = lst_t->link;
+       }
+       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..31d76d7
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       /* 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);
+               }
+       }
+
+       fflush_stdout_and_exit(0);
+}
diff --git a/coreutils/printf.c b/coreutils/printf.c
new file mode 100644 (file)
index 0000000..a9ef61f
--- /dev/null
@@ -0,0 +1,313 @@
+/* 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"
+
+typedef void (*converter)(const char *arg, void *result);
+
+static void multiconvert(const char *arg, void *result, converter convert)
+{
+       char s[sizeof(int)*3 + 2];
+
+       if (*arg == '"' || *arg == '\'') {
+               sprintf(s, "%d", (unsigned char)arg[1]);
+               arg = s;
+       }
+       convert(arg, result);
+       /* if there was conversion error, print unconverted string */
+       if (errno)
+               fputs(arg, stderr);
+}
+
+static void conv_strtoul(const char *arg, void *result)
+{
+       *(unsigned long*)result = bb_strtoul(arg, NULL, 0);
+}
+static void conv_strtol(const char *arg, void *result)
+{
+       *(long*)result = bb_strtol(arg, NULL, 0);
+}
+static void conv_strtod(const char *arg, void *result)
+{
+       char *end;
+       /* Well, this one allows leading whitespace... so what */
+       /* What I like much less is that "-" is accepted too! :( */
+       *(double*)result = strtod(arg, &end);
+       if (end[0]) errno = ERANGE;
+}
+
+static unsigned long my_xstrtoul(const char *arg)
+{
+       unsigned long result;
+       multiconvert(arg, &result, conv_strtoul);
+       return result;
+}
+
+static long my_xstrtol(const char *arg)
+{
+       long result;
+       multiconvert(arg, &result, conv_strtol);
+       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)
+{
+       for (; *str; str++) {
+               if (*str == '\\') {
+                       str++;
+                       bb_putchar(bb_process_escape_sequence((const char **)&str));
+               } else {
+                       bb_putchar(*str);
+               }
+
+       }
+}
+
+static void print_direc(char *start, size_t length, int field_width, int precision,
+               const char *argument)
+{
+       char *p;                /* Null-terminated copy of % directive. */
+
+       p = xmalloc((unsigned) (length + 1));
+       strncpy(p, start, length);
+       p[length] = 0;
+
+       switch (p[length - 1]) {
+       case 'd':
+       case 'i':
+               if (field_width < 0) {
+                       if (precision < 0)
+                               printf(p, my_xstrtol(argument));
+                       else
+                               printf(p, precision, my_xstrtol(argument));
+               } else {
+                       if (precision < 0)
+                               printf(p, field_width, my_xstrtol(argument));
+                       else
+                               printf(p, field_width, precision, my_xstrtol(argument));
+               }
+               break;
+       case 'o':
+       case 'u':
+       case 'x':
+       case 'X':
+               if (field_width < 0) {
+                       if (precision < 0)
+                               printf(p, my_xstrtoul(argument));
+                       else
+                               printf(p, precision, my_xstrtoul(argument));
+               } else {
+                       if (precision < 0)
+                               printf(p, field_width, my_xstrtoul(argument));
+                       else
+                               printf(p, field_width, precision, my_xstrtoul(argument));
+               }
+               break;
+       case 'f':
+       case 'e':
+       case 'E':
+       case 'g':
+       case 'G':
+               if (field_width < 0) {
+                       if (precision < 0)
+                               printf(p, my_xstrtod(argument));
+                       else
+                               printf(p, precision, my_xstrtod(argument));
+               } else {
+                       if (precision < 0)
+                               printf(p, field_width, my_xstrtod(argument));
+                       else
+                               printf(p, field_width, precision, my_xstrtod(argument));
+               }
+               break;
+       case 'c':
+               printf(p, *argument);
+               break;
+       case 's':
+               if (field_width < 0) {
+                       if (precision < 0)
+                               printf(p, argument);
+                       else
+                               printf(p, precision, argument);
+               } else {
+                       if (precision < 0)
+                               printf(p, field_width, argument);
+                       else
+                               printf(p, field_width, precision, argument);
+               }
+               break;
+       }
+
+       free(p);
+}
+
+/* Print the text in FORMAT, using ARGV (with ARGC elements) for
+   arguments to any '%' directives.
+   Return the number of elements of ARGV used.  */
+
+static int print_formatted(char *format, int argc, char **argv)
+{
+       int save_argc = argc;   /* Preserve original value.  */
+       char *f;                /* Pointer into 'format'.  */
+       char *direc_start;      /* Start of % directive.  */
+       size_t direc_length;    /* Length of % directive.  */
+       int field_width;        /* Arg to first '*', or -1 if none.  */
+       int precision;          /* Arg to second '*', or -1 if none.  */
+
+       for (f = format; *f; ++f) {
+               switch (*f) {
+               case '%':
+                       direc_start = f++;
+                       direc_length = 1;
+                       field_width = precision = -1;
+                       if (*f == '%') {
+                               bb_putchar('%');
+                               break;
+                       }
+                       if (*f == 'b') {
+                               if (argc > 0) {
+                                       print_esc_string(*argv);
+                                       ++argv;
+                                       --argc;
+                               }
+                               break;
+                       }
+                       if (strchr("-+ #", *f)) {
+                               ++f;
+                               ++direc_length;
+                       }
+                       if (*f == '*') {
+                               ++f;
+                               ++direc_length;
+                               if (argc > 0) {
+                                       field_width = my_xstrtoul(*argv);
+                                       ++argv;
+                                       --argc;
+                               } else
+                                       field_width = 0;
+                       } else {
+                               while (isdigit(*f)) {
+                                       ++f;
+                                       ++direc_length;
+                               }
+                       }
+                       if (*f == '.') {
+                               ++f;
+                               ++direc_length;
+                               if (*f == '*') {
+                                       ++f;
+                                       ++direc_length;
+                                       if (argc > 0) {
+                                               precision = my_xstrtoul(*argv);
+                                               ++argv;
+                                               --argc;
+                                       } else
+                                               precision = 0;
+                               } else
+                                       while (isdigit(*f)) {
+                                               ++f;
+                                               ++direc_length;
+                                       }
+                       }
+                       if (*f == 'l' || *f == 'L' || *f == 'h') {
+                               ++f;
+                               ++direc_length;
+                       }
+                       /*
+                       if (!strchr ("diouxXfeEgGcs", *f))
+                       fprintf(stderr, "%%%c: invalid directive", *f);
+                       */
+                       ++direc_length;
+                       if (argc > 0) {
+                               print_direc(direc_start, direc_length, field_width,
+                                                       precision, *argv);
+                               ++argv;
+                               --argc;
+                       } else
+                               print_direc(direc_start, direc_length, field_width,
+                                                       precision, "");
+                       break;
+               case '\\':
+                       if (*++f == 'c')
+                               exit(0);
+                       bb_putchar(bb_process_escape_sequence((const char **)&f));
+                       f--;
+                       break;
+               default:
+                       bb_putchar(*f);
+               }
+       }
+
+       return save_argc - argc;
+}
+
+int printf_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int printf_main(int argc, char **argv)
+{
+       char *format;
+       int args_used;
+
+       if (argc <= 1 || argv[1][0] == '-') {
+               bb_show_usage();
+       }
+
+       format = argv[1];
+       argc -= 2;
+       argv += 2;
+
+       do {
+               args_used = print_formatted(format, argc, argv);
+               argc -= args_used;
+               argv += args_used;
+       } while (args_used > 0 && argc > 0);
+
+/*     if (argc > 0)
+               fprintf(stderr, "excess args ignored");
+*/
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/pwd.c b/coreutils/pwd.c
new file mode 100644 (file)
index 0000000..9279dbe
--- /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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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..3f13a36
--- /dev/null
@@ -0,0 +1,50 @@
+/* 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 <getopt.h>
+#include "libbb.h"
+
+int readlink_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int readlink_main(int argc ATTRIBUTE_UNUSED, 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, "f");
+               fname = argv[optind];
+       )
+       SKIP_FEATURE_READLINK_FOLLOW(
+               const unsigned opt = 0;
+               if (argc != 2) bb_show_usage();
+               fname = argv[1];
+       )
+
+       /* compat: coreutils readlink reports errors silently via exit code */
+       logmode = LOGMODE_NONE;
+
+       if (opt) {
+               buf = realpath(fname, pathbuf);
+       } else {
+               buf = xmalloc_readlink_or_warn(fname);
+       }
+
+       if (!buf)
+               return EXIT_FAILURE;
+       puts(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..6766524
--- /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 ATTRIBUTE_UNUSED, 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..1774ce2
--- /dev/null
@@ -0,0 +1,55 @@
+/* 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       int status = 0;
+       int flags = 0;
+       unsigned opt;
+
+       opt_complementary = "f-i:i-f";
+       opt = getopt32(argv, "fiRr");
+       argv += optind;
+       if (opt & 1)
+               flags |= FILEUTILS_FORCE;
+       if (opt & 2)
+               flags |= FILEUTILS_INTERACTIVE;
+       if (opt & 12)
+               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..cb60466
--- /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 ATTRIBUTE_UNUSED, 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..01d71f2
--- /dev/null
@@ -0,0 +1,40 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * seq implementation for busybox
+ *
+ * Copyright (C) 2004, Glenn McGrath
+ *
+ * Licensed under the GPL v2, see the file LICENSE in this tarball.
+ */
+
+#include "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)
+{
+       double last, increment, i;
+
+       i = increment = 1;
+       switch (argc) {
+               case 4:
+                       increment = atof(argv[2]);
+               case 3:
+                       i = atof(argv[1]);
+               case 2:
+                       last = atof(argv[argc-1]);
+                       break;
+               default:
+                       bb_show_usage();
+       }
+
+       /* You should note that this is pos-5.0.91 semantics, -- FK. */
+       while ((increment > 0 && i <= last) || (increment < 0 && i >= last)) {
+               printf("%g\n", i);
+               i += increment;
+       }
+
+       return fflush(stdout);
+}
diff --git a/coreutils/sleep.c b/coreutils/sleep.c
new file mode 100644 (file)
index 0000000..78f9a8e
--- /dev/null
@@ -0,0 +1,63 @@
+/* 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
+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 ATTRIBUTE_UNUSED, char **argv)
+{
+       unsigned duration;
+
+       ++argv;
+       if (!*argv)
+               bb_show_usage();
+
+#if ENABLE_FEATURE_FANCY_SLEEP
+
+       duration = 0;
+       do {
+               duration += xatoul_range_sfx(*argv, 0, UINT_MAX-duration, sfx);
+       } while (*++argv);
+
+#else  /* FEATURE_FANCY_SLEEP */
+
+       duration = xatou(*argv);
+
+#endif /* FEATURE_FANCY_SLEEP */
+
+       if (sleep(duration)) {
+               bb_perror_nomsg_and_die();
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/sort.c b/coreutils/sort.c
new file mode 100644 (file)
index 0000000..15566ce
--- /dev/null
@@ -0,0 +1,408 @@
+/* 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, i, j;
+
+       /* Special case whole string, so we don't have to make a copy */
+       if (key->range[0] == 1 && !key->range[1] && !key->range[2] && !key->range[3]
+        && !(flags & (FLAG_b | FLAG_d | FLAG_f | FLAG_i | FLAG_bb))
+       ) {
+               return str;
+       }
+
+       /* Find start of key on first pass, end on second pass */
+       len = strlen(str);
+       for (j = 0; j < 2; j++) {
+               if (!key->range[2*j])
+                       end = len;
+               /* Loop through fields */
+               else {
+                       end = 0;
+                       for (i = 1; i < key->range[2*j] + j; i++) {
+                               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_getline(fp))
+#else
+#define GET_LINE(fp) xmalloc_getline(fp)
+#endif
+
+/* Iterate through keys list and perform comparisons */
+static int compare_keys(const void *xarg, const void *yarg)
+{
+       int flags = 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 ATTRIBUTE_UNUSED, 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(str_o, "w");
+       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 = lst_k->data;
+               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++;
+                       }
+               }
+               /* leaking lst_k... */
+               lst_k = lst_k->link;
+       }
+#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;
+                       if (!(linecount & 63))
+                               lines = xrealloc(lines, sizeof(char *) * (linecount + 64));
+                       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..2306789
--- /dev/null
@@ -0,0 +1,139 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * split - split a file into pieces
+ * Copyright (c) 2007 Bernhard 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 ATTRIBUTE_UNUSED, 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(0, 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(1, 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..b2b1913
--- /dev/null
@@ -0,0 +1,654 @@
+/* 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;
+}
+
+#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, "Lx");
+               printf(pformat, statfsbuf->f_fsid);
+       } else if (m == 'l') {
+               strcat(pformat, "lu");
+               printf(pformat, 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 != 0L) ? 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 != 0L) ? 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)
+{
+#if !ENABLE_FEATURE_STAT_FORMAT
+       const char *format;
+#endif
+       struct statfs statfsbuf;
+#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: %-8Lx Namelen: %-7lu ");
+       printf(format,
+              filename,
+              statfsbuf.f_fsid,
+              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 != 0L) ? pw_ent->pw_name : "UNKNOWN",
+                      (unsigned long) statbuf.st_gid,
+                      (gw_ent != 0L) ? 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..298fb5b
--- /dev/null
@@ -0,0 +1,1440 @@
+/* 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 ATTRIBUTE_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;
+       int 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 ATTRIBUTE_UNUSED 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..e6cfbfd
--- /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 ATTRIBUTE_UNUSED, 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..5c9d092
--- /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 ATTRIBUTE_UNUSED)
+{
+       /* 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..af70f30
--- /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 ATTRIBUTE_UNUSED, 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..2f997a9
--- /dev/null
@@ -0,0 +1,283 @@
+/* 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, seen, i, opt;
+
+       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][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 > 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 < 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 < 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..b388017
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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"
+#include <signal.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 = fopen_or_warn(*argv, mode);
+               if (*fp == NULL) {
+                       retval = EXIT_FAILURE;
+                       continue;
+               }
+               *np = *argv++;
+ GOT_NEW_FILE:
+               setbuf(*fp++, NULL);    /* tee must not buffer output. */
+               np++;
+       } while (*argv);
+       /* names[0] will be filled later */
+
+#if ENABLE_FEATURE_TEE_USE_BLOCK_IO
+       while ((c = safe_read(STDIN_FILENO, buf, 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..2f5b6b8
--- /dev/null
@@ -0,0 +1,638 @@
+/* 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 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>
+*/
+
+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)
+enum token_types {
+       UNOP,
+       BINOP,
+       BUNOP,
+       BBINOP,
+       PAREN
+};
+
+static const struct t_op {
+       char op_text[4];
+       unsigned char op_num, op_type;
+} ops[] = {
+       { "-r", FILRD   , UNOP   },
+       { "-w", FILWR   , UNOP   },
+       { "-x", FILEX   , UNOP   },
+       { "-e", FILEXIST, UNOP   },
+       { "-f", FILREG  , UNOP   },
+       { "-d", FILDIR  , UNOP   },
+       { "-c", FILCDEV , UNOP   },
+       { "-b", FILBDEV , UNOP   },
+       { "-p", FILFIFO , UNOP   },
+       { "-u", FILSUID , UNOP   },
+       { "-g", FILSGID , UNOP   },
+       { "-k", FILSTCK , UNOP   },
+       { "-s", FILGZ   , UNOP   },
+       { "-t", FILTT   , UNOP   },
+       { "-z", STREZ   , UNOP   },
+       { "-n", STRNZ   , UNOP   },
+       { "-h", FILSYM  , UNOP   },    /* for backwards compat */
+
+       { "-O" , FILUID , UNOP   },
+       { "-G" , FILGID , UNOP   },
+       { "-L" , FILSYM , UNOP   },
+       { "-S" , FILSOCK, UNOP   },
+       { "="  , STREQ  , BINOP  },
+       { "==" , STREQ  , BINOP  },
+       { "!=" , STRNE  , BINOP  },
+       { "<"  , STRLT  , BINOP  },
+       { ">"  , STRGT  , BINOP  },
+       { "-eq", INTEQ  , BINOP  },
+       { "-ne", INTNE  , BINOP  },
+       { "-ge", INTGE  , BINOP  },
+       { "-gt", INTGT  , BINOP  },
+       { "-le", INTLE  , BINOP  },
+       { "-lt", INTLT  , BINOP  },
+       { "-nt", FILNT  , BINOP  },
+       { "-ot", FILOT  , BINOP  },
+       { "-ef", FILEQ  , BINOP  },
+       { "!"  , UNOT   , BUNOP  },
+       { "-a" , BAND   , BBINOP },
+       { "-o" , BOR    , BBINOP },
+       { "("  , LPAREN , PAREN  },
+       { ")"  , RPAREN , PAREN  },
+};
+
+
+#if ENABLE_FEATURE_TEST_64
+typedef int64_t arith_t;
+#else
+typedef int arith_t;
+#endif
+
+
+/* We try to minimize both static and stack usage. */
+struct statics {
+       char **t_wp;
+       const struct t_op *t_wp_op;
+       gid_t *group_array;
+       int ngroups;
+       jmp_buf leaving;
+};
+
+/* Make it reside in writable memory, yet make compiler understand
+ * that it is not going to change. */
+static struct statics *const ptr_to_statics __attribute__ ((section (".data")));
+
+#define S (*ptr_to_statics)
+#define t_wp            (S.t_wp         )
+#define t_wp_op         (S.t_wp_op      )
+#define group_array     (S.group_array  )
+#define ngroups         (S.ngroups      )
+#define leaving         (S.leaving      )
+
+#define INIT_S() do { \
+       (*(struct statics**)&ptr_to_statics) = xzalloc(sizeof(S)); \
+       barrier(); \
+} while (0)
+#define DEINIT_S() do { \
+       free(ptr_to_statics); \
+} while (0)
+
+static arith_t primary(enum token n);
+
+static void syntax(const char *op, const char *msg) ATTRIBUTE_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 arith_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 t_lex(char *s)
+{
+       const struct t_op *op;
+
+       t_wp_op = NULL;
+       if (s == NULL) {
+               return EOI;
+       }
+
+       op = ops;
+       do {
+               if (strcmp(s, op->op_text) == 0) {
+                       t_wp_op = op;
+                       return op->op_num;
+               }
+               op++;
+       } while (op < ops + ARRAY_SIZE(ops));
+
+       return OPERAND;
+}
+
+
+static int binop(void)
+{
+       const char *opnd1, *opnd2;
+       struct t_op const *op;
+       arith_t val1, val2;
+
+       opnd1 = *t_wp;
+       (void) t_lex(*++t_wp);
+       op = t_wp_op;
+
+       opnd2 = *++t_wp;
+       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;
+       int 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 arith_t nexpr(enum token n)
+{
+       if (n == UNOT)
+               return !nexpr(t_lex(*++t_wp));
+       return primary(n);
+}
+
+
+static arith_t aexpr(enum token n)
+{
+       arith_t res;
+
+       res = nexpr(n);
+       if (t_lex(*++t_wp) == BAND)
+               return aexpr(t_lex(*++t_wp)) && res;
+       t_wp--;
+       return res;
+}
+
+
+static arith_t oexpr(enum token n)
+{
+       arith_t res;
+
+       res = aexpr(n);
+       if (t_lex(*++t_wp) == BOR) {
+               return oexpr(t_lex(*++t_wp)) || res;
+       }
+       t_wp--;
+       return res;
+}
+
+
+
+static arith_t primary(enum token n)
+{
+       arith_t res;
+
+       if (n == EOI) {
+               syntax(NULL, "argument expected");
+       }
+       if (n == LPAREN) {
+               res = oexpr(t_lex(*++t_wp));
+               if (t_lex(*++t_wp) != RPAREN)
+                       syntax(NULL, "closing paren expected");
+               return res;
+       }
+       if (t_wp_op && t_wp_op->op_type == UNOP) {
+               /* unary expression */
+               if (*++t_wp == NULL)
+                       syntax(t_wp_op->op_text, "argument expected");
+               if (n == STREZ)
+                       return t_wp[0][0] == '\0';
+               if (n == STRNZ)
+                       return t_wp[0][0] != '\0';
+               if (n == FILTT)
+                       return isatty(getn(*t_wp));
+               return filstat(*t_wp, n);
+       }
+
+       t_lex(t_wp[1]);
+       if (t_wp_op && t_wp_op->op_type == BINOP) {
+               return binop();
+       }
+
+       return t_wp[0][0] != '\0';
+}
+
+
+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 (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]) {
+               t_lex(argv[1]);
+               if (t_wp_op && t_wp_op->op_type == BINOP) {
+                       /* "test [!] arg1 <binary_op> arg2" */
+                       t_wp = &argv[0];
+                       res = (binop() == 0);
+                       goto ret;
+               }
+       }
+
+       /* Some complex expression. Undo '!' removal */
+       if (negate) {
+               negate = 0;
+               //argc++;
+               argv--;
+       }
+       t_wp = &argv[0];
+       res = !oexpr(t_lex(*t_wp));
+
+       if (*t_wp != NULL && *++t_wp != NULL) {
+               bb_error_msg("%s: unknown operand", *t_wp);
+               res = 2;
+       }
+ ret:
+       DEINIT_S();
+       return negate ? !res : res;
+}
diff --git a/coreutils/touch.c b/coreutils/touch.c
new file mode 100644 (file)
index 0000000..0b58179
--- /dev/null
@@ -0,0 +1,58 @@
+/* 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! */
+
+int touch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int touch_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int fd;
+       int status = EXIT_SUCCESS;
+       int flags = getopt32(argv, "cf");
+
+       flags &= 1; /* ignoring -f (BSD compat thingy) */
+       argv += optind;
+
+       if (!*argv) {
+               bb_show_usage();
+       }
+
+       do {
+               if (utime(*argv, NULL)) {
+                       if (errno == ENOENT) {  /* no such file */
+                               if (flags) {    /* Creation is disabled, so ignore. */
+                                       continue;
+                               }
+                               /* Try to create the file. */
+                               fd = open(*argv, O_RDWR | O_CREAT,
+                                                 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
+                                                 );
+                               if ((fd >= 0) && !close(fd)) {
+                                       continue;
+                               }
+                       }
+                       status = EXIT_FAILURE;
+                       bb_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..d0af63a
--- /dev/null
@@ -0,0 +1,246 @@
+/* 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: xdigit, graph, print
+ */
+#include "libbb.h"
+
+#define ASCII 0377
+
+static void map(char *pvector,
+               unsigned char *string1, unsigned int string1_len,
+               unsigned char *string2, unsigned int string2_len)
+{
+       char last = '0';
+       unsigned int i, j;
+
+       for (j = 0, i = 0; i < string1_len; i++) {
+               if (string2_len <= j)
+                       pvector[string1[i]] = last;
+               else
+                       pvector[string1[i]] = last = string2[j++];
+       }
+}
+
+/* supported constructs:
+ *   Ranges,  e.g.,  0-9   ==>  0123456789
+ *   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?)
+ */
+static unsigned int expand(const char *arg, char *buffer)
+{
+       char *buffer_start = buffer;
+       unsigned i; /* can't be unsigned char: must be able to hold 256 */
+       unsigned char ac;
+
+       while (*arg) {
+               if (*arg == '\\') {
+                       arg++;
+                       *buffer++ = bb_process_escape_sequence(&arg);
+                       continue;
+               }
+               if (arg[1] == '-') { /* "0-9..." */
+                       ac = arg[2];
+                       if (ac == '\0') { /* "0-": copy verbatim */
+                               *buffer++ = *arg++; /* copy '0' */
+                               continue; /* next iter will copy '-' and stop */
+                       }
+                       i = *arg;
+                       while (i <= ac) /* ok: i is unsigned _int_ */
+                               *buffer++ = i++;
+                       arg += 3; /* skip 0-9 */
+                       continue;
+               }
+               if (*arg == '[') { /* "[xyz..." */
+                       arg++;
+                       i = *arg++;
+                       /* "[xyz...", i=x, arg points to y */
+                       if (ENABLE_FEATURE_TR_CLASSES && i == ':') {
+#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;
+#define CLASS_invalid 0 /* we increment the retval */
+#define CLASS_alpha 1
+#define CLASS_alnum 2
+#define CLASS_digit 3
+#define CLASS_lower 4
+#define CLASS_upper 5
+#define CLASS_space 6
+#define CLASS_blank 7
+#define CLASS_punct 8
+#define CLASS_cntrl 9
+//#define CLASS_xdigit 10
+//#define CLASS_graph 11
+//#define CLASS_print 12
+                               smalluint j;
+                               { /* not really pretty.. */
+                                       char *tmp = xstrndup(arg, 7); // warning: xdigit would need 8, not 7
+                                       j = index_in_strings(classes, tmp) + 1;
+                                       free(tmp);
+                               }
+                               if (j == CLASS_alnum || j == CLASS_digit) {
+                                       for (i = '0'; i <= '9'; i++)
+                                               *buffer++ = i;
+                               }
+                               if (j == CLASS_alpha || j == CLASS_alnum || j == CLASS_upper) {
+                                       for (i = 'A'; i <= 'Z'; i++)
+                                               *buffer++ = i;
+                               }
+                               if (j == CLASS_alpha || j == CLASS_alnum || j == CLASS_lower) {
+                                       for (i = 'a'; i <= 'z'; i++)
+                                               *buffer++ = i;
+                               }
+                               if (j == CLASS_space || j == CLASS_blank) {
+                                       *buffer++ = '\t';
+                                       if (j == CLASS_space) {
+                                               *buffer++ = '\n';
+                                               *buffer++ = '\v';
+                                               *buffer++ = '\f';
+                                               *buffer++ = '\r';
+                                       }
+                                       *buffer++ = ' ';
+                               }
+                               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++ = i;
+                               }
+                               if (j == CLASS_invalid) {
+                                       *buffer++ = '[';
+                                       *buffer++ = ':';
+                                       continue;
+                               }
+                               break;
+                       }
+                       /* "[xyz...", i=x, arg points to y */
+                       if (ENABLE_FEATURE_TR_EQUIV && i == '=') { /* [=CHAR=] */
+                               *buffer++ = *arg; /* copy CHAR */
+                               arg += 3;       /* skip CHAR=] */
+                               continue;
+                       }
+                       if (*arg != '-') { /* not [x-...] - copy verbatim */
+                               *buffer++ = '[';
+                               arg--; /* points to x */
+                               continue; /* copy all, including eventual ']' */
+                       }
+                       /* [x-y...] */
+                       arg++;
+                       ac = *arg++;
+                       while (i <= ac)
+                               *buffer++ = i++;
+                       arg++;  /* skip the assumed ']' */
+                       continue;
+               }
+               *buffer++ = *arg++;
+       }
+       return (buffer - buffer_start);
+}
+
+static int complement(char *buffer, int buffer_len)
+{
+       int i, j, ix;
+       char conv[ASCII + 2];
+
+       ix = 0;
+       for (i = '\0'; i <= ASCII; i++) {
+               for (j = 0; j < buffer_len; j++)
+                       if (buffer[j] == i)
+                               break;
+               if (j == buffer_len)
+                       conv[ix++] = i & ASCII;
+       }
+       memcpy(buffer, conv, ix);
+       return ix;
+}
+
+int tr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tr_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int output_length = 0, input_length;
+       int i;
+       smalluint flags;
+       ssize_t read_chars = 0;
+       size_t in_index = 0, out_index = 0;
+       unsigned last = UCHAR_MAX + 1; /* not equal to any char */
+       unsigned char coded, c;
+       unsigned char *output = xmalloc(BUFSIZ);
+       char *vector = xzalloc((ASCII+1) * 3);
+       char *invec  = vector + (ASCII+1);
+       char *outvec = vector + (ASCII+1) * 2;
+
+#define TR_OPT_complement      (1 << 0)
+#define TR_OPT_delete          (1 << 1)
+#define TR_OPT_squeeze_reps    (1 << 2)
+
+       flags = getopt32(argv, "+cds"); /* '+': stop at first non-option */
+       argv += optind;
+
+       for (i = 0; i <= ASCII; i++) {
+               vector[i] = i;
+               /*invec[i] = outvec[i] = FALSE; - done by xzalloc */
+       }
+
+#define tr_buf bb_common_bufsiz1
+       if (*argv != NULL) {
+               input_length = expand(*argv++, tr_buf);
+               if (flags & TR_OPT_complement)
+                       input_length = complement(tr_buf, input_length);
+               if (*argv) {
+                       if (argv[0][0] == '\0')
+                               bb_error_msg_and_die("STRING2 cannot be empty");
+                       output_length = expand(*argv, output);
+                       map(vector, tr_buf, input_length, output, output_length);
+               }
+               for (i = 0; i < input_length; i++)
+                       invec[(unsigned char)tr_buf[i]] = TRUE;
+               for (i = 0; i < output_length; i++)
+                       outvec[output[i]] = TRUE;
+       }
+
+       for (;;) {
+               /* If we're out of input, flush output and read more input. */
+               if (in_index == read_chars) {
+                       if (out_index) {
+                               xwrite(STDOUT_FILENO, (char *)output, out_index);
+                               out_index = 0;
+                       }
+                       read_chars = safe_read(STDIN_FILENO, tr_buf, BUFSIZ);
+                       if (read_chars <= 0) {
+                               if (read_chars < 0)
+                                       bb_perror_msg_and_die(bb_msg_read_error);
+                               exit(EXIT_SUCCESS);
+                       }
+                       in_index = 0;
+               }
+               c = tr_buf[in_index++];
+               coded = vector[c];
+               if ((flags & TR_OPT_delete) && invec[c])
+                       continue;
+               if ((flags & TR_OPT_squeeze_reps) && last == coded
+                && (invec[c] || outvec[coded]))
+                       continue;
+               output[out_index++] = last = coded;
+       }
+       /* NOTREACHED */
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/true.c b/coreutils/true.c
new file mode 100644 (file)
index 0000000..565e68b
--- /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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       return EXIT_SUCCESS;
+}
diff --git a/coreutils/tty.c b/coreutils/tty.c
new file mode 100644 (file)
index 0000000..48e1511
--- /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(ATTRIBUTE_UNUSED))
+{
+       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 = 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..2eecb5d
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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> */
+
+/* Busyboxed by Erik Andersen */
+
+/* Further size reductions by Glenn McGrath and Manuel Novoa III. */
+
+/* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
+ *
+ * Now does proper error checking on i/o.  Plus some further space savings.
+ */
+
+#include <sys/utsname.h>
+#include "libbb.h"
+
+typedef struct {
+       struct utsname name;
+       char processor[8];                      /* for "unknown" */
+} uname_info_t;
+
+static const char options[] ALIGN1 = "snrvmpa";
+static const unsigned short utsname_offset[] ALIGN2 = {
+       offsetof(uname_info_t,name.sysname),
+       offsetof(uname_info_t,name.nodename),
+       offsetof(uname_info_t,name.release),
+       offsetof(uname_info_t,name.version),
+       offsetof(uname_info_t,name.machine),
+       offsetof(uname_info_t,processor)
+};
+
+int uname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int uname_main(int argc, char **argv)
+{
+       uname_info_t uname_info;
+#if defined(__sparc__) && defined(__linux__)
+       char *fake_sparc = getenv("FAKE_SPARC");
+#endif
+       const unsigned short int *delta;
+       char toprint;
+
+       toprint = getopt32(argv, options);
+
+       if (argc != optind) {
+               bb_show_usage();
+       }
+
+       if (toprint & (1 << 6)) {
+               toprint = 0x3f;
+       }
+
+       if (toprint == 0) {
+               toprint = 1;                    /* sysname */
+       }
+
+       if (uname(&uname_info.name) == -1) {
+               bb_error_msg_and_die("cannot get system name");
+       }
+
+#if defined(__sparc__) && defined(__linux__)
+       if ((fake_sparc != NULL)
+               && ((fake_sparc[0] == 'y')
+                       || (fake_sparc[0] == 'Y'))) {
+               strcpy(uname_info.name.machine, "sparc");
+       }
+#endif
+
+       strcpy(uname_info.processor, "unknown");
+
+       delta = utsname_offset;
+       do {
+               if (toprint & 1) {
+                       printf(((char *)(&uname_info)) + *delta);
+                       if (toprint > 1) {
+                               bb_putchar(' ');
+                       }
+               }
+               ++delta;
+       } while (toprint >>= 1);
+       bb_putchar('\n');
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/coreutils/uniq.c b/coreutils/uniq.c
new file mode 100644 (file)
index 0000000..d072960
--- /dev/null
@@ -0,0 +1,103 @@
+/* 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 const char uniq_opts[] ALIGN1 = "cdu" "f:s:" "cdu\0\1\2\4";
+
+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 ATTRIBUTE_UNUSED, char **argv)
+{
+       FILE *in, *out;
+       unsigned long dups, skip_fields, skip_chars, i;
+       const char *s0, *e0, *s1, *e1, *input_filename;
+       unsigned opt;
+
+       enum {
+               OPT_c = 0x1,
+               OPT_d = 0x2,
+               OPT_u = 0x4,
+               OPT_f = 0x8,
+               OPT_s = 0x10,
+       };
+
+       skip_fields = skip_chars = 0;
+
+       opt = getopt32(argv, "cduf:s:", &s0, &s1);
+       if (opt & OPT_f)
+               skip_fields = xatoul(s0);
+       if (opt & OPT_s)
+               skip_chars = xatoul(s1);
+       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_getline(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 || strcmp(e0, e1)) {
+                               break;
+                       }
+
+                       ++dups;          /* Note: Testing for overflow seems excessive. */
+               }
+
+               if (s0) {
+                       if (!(opt & (OPT_d << !!dups))) { /* (if dups, opt & OPT_e) */
+                               fprintf(out, "\0%d " + (opt & 1), dups + 1);
+                               fprintf(out, "%s\n", s0);
+                       }
+                       free((void *)s0);
+               }
+       } while (s1);
+
+       die_if_ferror(in, input_filename);
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/coreutils/usleep.c b/coreutils/usleep.c
new file mode 100644 (file)
index 0000000..d34880d
--- /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 ATTRIBUTE_UNUSED, 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..4c619de
--- /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_getline(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 ATTRIBUTE_UNUSED, 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_getline(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(outname, "w");
+                       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..de3c895
--- /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 ATTRIBUTE_UNUSED, 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..a206ec5
--- /dev/null
@@ -0,0 +1,76 @@
+/* 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>
+#include <time.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 ATTRIBUTE_UNUSED, 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)) {
+                       /* 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);
+                       /* 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(&(ut->ut_tv.tv_sec)) + 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..d35572e
--- /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 ATTRIBUTE_UNUSED)
+{
+       if (argc > 1)
+               bb_show_usage();
+
+       /* Will complain and die if username not found */
+       puts(bb_getpwuid(NULL, -1, 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..f1b73b6
--- /dev/null
@@ -0,0 +1,83 @@
+#
+# 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
+
+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..b011fc1
--- /dev/null
@@ -0,0 +1,50 @@
+/* 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.
+ */
+
+#include "libbb.h"
+
+int mktemp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mktemp_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       // -d      Make a directory instead of a file
+       // -q      Fail silently if an error occurs [bbox: ignored]
+       // -t      Generate a path rooted in temporary directory
+       // -p DIR  Use DIR as a temporary directory (implies -t)
+       const char *path;
+       char *chp;
+       unsigned flags;
+
+       opt_complementary = "=1"; /* exactly one arg */
+       flags = getopt32(argv, "dqtp:", &path);
+       chp = argv[optind];
+
+       if (flags & (4|8)) { /* -t and/or -p */
+               const char *dir = getenv("TMPDIR");
+               if (dir && *dir != '\0')
+                       path = dir;
+               else if (!(flags & 8)) /* No -p */
+                       path = "/tmp/";
+               /* else path comes from -p DIR */
+               chp = concat_path_file(path, chp);
+       }
+
+       if (flags & 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..cbdd38f
--- /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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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..2adad02
--- /dev/null
@@ -0,0 +1,177 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini run-parts implementation for busybox
+ *
+ * Copyright (C) 2007 Bernhard 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 <getopt.h>
+
+#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(struct globals)) / sizeof(cmd[0]) };
+
+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 act(const char *file, struct stat *statbuf, void *args ATTRIBUTE_UNUSED, 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(names, (cur + 2) * sizeof(names[0]));
+       names[cur++] = xstrdup(file);
+       names[cur] = NULL;
+
+       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 ATTRIBUTE_UNUSED, 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] = arg_list->data;
+               arg_list = arg_list->link;
+               n++;
+       }
+       /* 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..4e816bd
--- /dev/null
@@ -0,0 +1,364 @@
+/* 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.
+ */
+
+/* NB: we have a problem here with /proc/NN/exe usage, similar to
+ * one fixed in killall/pidof */
+
+#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;
+};
+
+
+struct globals {
+       struct pid_list *found;
+       char *userspec;
+       char *cmdname;
+       char *execname;
+       char *pidfile;
+       int user_id;
+       smallint quiet;
+       smallint signal_nr;
+       struct stat execstat;
+};
+#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 quiet             (G.quiet               )
+#define signal_nr         (G.signal_nr           )
+#define execstat          (G.execstat            )
+#define INIT_G() \
+        do { \
+               user_id = -1; \
+               signal_nr = 15; \
+        } while (0)
+
+
+static int pid_is_exec(pid_t pid)
+{
+       struct stat st;
+       char buf[sizeof("/proc//exe") + sizeof(int)*3];
+
+       sprintf(buf, "/proc/%u/exe", 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;
+}
+
+static int pid_is_user(int pid)
+{
+       struct stat sb;
+       char buf[sizeof("/proc/") + sizeof(int)*3];
+
+       sprintf(buf, "/proc/%u", pid);
+       if (stat(buf, &sb) != 0)
+               return 0;
+       return (sb.st_uid == user_id);
+}
+
+static int pid_is_cmd(pid_t pid)
+{
+       char buf[256]; /* is it big enough? */
+       char *p, *pe;
+
+       sprintf(buf, "/proc/%u/stat", 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';
+       return !strcmp(p, cmdname);
+}
+
+static void check(int pid)
+{
+       struct pid_list *p;
+
+       if (execname && !pid_is_exec(pid)) {
+               return;
+       }
+       if (userspec && !pid_is_user(pid)) {
+               return;
+       }
+       if (cmdname && !pid_is_cmd(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(pidfile, "r");
+       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: 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 (kill(p->pid, signal_nr) == 0) {
+                       p->pid = - p->pid;
+                       killed++;
+               } else {
+                       bb_perror_msg("warning: killing process %u", p->pid);
+               }
+       }
+       if (!quiet && killed) {
+               printf("stopped %s (pid", what);
+               for (p = found; p; p = p->next)
+                       if (p->pid < 0)
+                               printf(" %u", - 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"
+       "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
+
+enum {
+       CTX_STOP       = 0x1,
+       CTX_START      = 0x2,
+       OPT_BACKGROUND = 0x4, // -b
+       OPT_QUIET      = 0x8, // -q
+       OPT_MAKEPID    = 0x10, // -m
+       OPT_a          = 0x20, // -a
+       OPT_n          = 0x40, // -n
+       OPT_s          = 0x80, // -s
+       OPT_u          = 0x100, // -u
+       OPT_c          = 0x200, // -c
+       OPT_x          = 0x400, // -x
+       OPT_p          = 0x800, // -p
+       OPT_OKNODO     = 0x1000 * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -o
+       OPT_VERBOSE    = 0x2000 * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -v
+       OPT_NICELEVEL  = 0x4000 * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -N
+};
+
+int start_stop_daemon_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int start_stop_daemon_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       unsigned opt;
+       char *signame;
+       char *startas;
+       char *chuid;
+#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
+
+       /* Check required one context option was given */
+       opt_complementary = "K:S:K--S:S--K:m?p:K?xpun:S?xa";
+       opt = getopt32(argv, "KSbqma:n:s:u:c:x:p:"
+               USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:"),
+//             USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:R:"),
+               &startas, &cmdname, &signame, &userspec, &chuid, &execname, &pidfile
+               USE_FEATURE_START_STOP_DAEMON_FANCY(,&opt_N)
+//             USE_FEATURE_START_STOP_DAEMON_FANCY(,&retry_arg)
+       );
+
+       quiet = (opt & OPT_QUIET) && !(opt & OPT_VERBOSE);
+
+       if (opt & OPT_s) {
+               signal_nr = get_signum(signame);
+               if (signal_nr < 0) bb_show_usage();
+       }
+
+       if (!(opt & OPT_a))
+               startas = execname;
+
+//     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);
+       }
+       if (execname)
+               xstat(execname, &execstat);
+
+       do_procinit(); /* Both start and stop needs to know current processes */
+
+       if (opt & CTX_STOP) {
+               int i = do_stop();
+               return (opt & OPT_OKNODO) ? 0 : (i <= 0);
+       }
+
+       if (found) {
+               if (!quiet)
+                       printf("%s already running\n%d\n", execname, found->pid);
+               return !(opt & OPT_OKNODO);
+       }
+       *--argv = startas;
+       if (opt & OPT_BACKGROUND) {
+#if BB_MMU
+               bb_daemonize(0);
+#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(0);
+               }
+               /* 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;
+               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
+       execv(startas, argv);
+       bb_perror_msg_and_die("cannot start %s", startas);
+}
diff --git a/debianutils/which.c b/debianutils/which.c
new file mode 100644 (file)
index 0000000..5ab6719
--- /dev/null
@@ -0,0 +1,50 @@
+/* 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, char **argv)
+{
+       int status = EXIT_SUCCESS;
+       char *p;
+
+       if (argc <= 1 || argv[1][0] == '-') {
+               bb_show_usage();
+       }
+
+       /* This matches what is seen on e.g. ubuntu
+        * "which" there is a shell script */
+       if (!getenv("PATH")) {
+               putenv((char*)bb_PATH_root_path);
+       }
+
+       while (--argc > 0) {
+               argv++;
+               if (strchr(*argv, '/')) {
+                       if (execable_file(*argv)) {
+                               puts(*argv);
+                               continue;
+                       }
+               } else {
+                       p = find_execable(*argv);
+                       if (p) {
+                               puts(p);
+                               free(p);
+                               continue;
+                       }
+               }
+               status = EXIT_FAILURE;
+       }
+
+       fflush_stdout_and_exit(status);
+}
diff --git a/docs/autodocifier.pl b/docs/autodocifier.pl
new file mode 100755 (executable)
index 0000000..68b6f3c
--- /dev/null
@@ -0,0 +1,303 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Long;
+
+# collect lines continued with a '\' into an array
+sub continuation {
+       my $fh = shift;
+       my @line;
+
+       while (<$fh>) {
+               my $s = $_;
+               $s =~ s/\\\s*$//;
+               #$s =~ s/#.*$//;
+               push @line, $s;
+               last unless (/\\\s*$/);
+       }
+       return @line;
+}
+
+# regex && eval away unwanted strings from documentation
+sub beautify {
+       my $text = shift;
+       for (;;) {
+               my $text2 = $text;
+               $text =~ s/SKIP_\w+\(.*?"\s*\)//sxg;
+               $text =~ s/USE_\w+\(\s*?(.*?)"\s*\)/$1"/sxg;
+               $text =~ s/USAGE_\w+\(\s*?(.*?)"\s*\)/$1"/sxg;
+               last if ( $text2 eq $text );
+       }
+       $text =~ s/"\s*"//sg;
+       my @line = split("\n", $text);
+       $text = join('',
+               map {
+                       s/^\s*"//;
+                       s/"\s*$//;
+                       s/%/%%/g;
+                       s/\$/\\\$/g;
+                       eval qq[ sprintf(qq{$_}) ]
+               } @line
+       );
+       return $text;
+}
+
+# generate POD for an applet
+sub pod_for_usage {
+       my $name  = shift;
+       my $usage = shift;
+
+       # Sigh.  Fixup the known odd-name applets.
+       $name =~ s/dpkg_deb/dpkg-deb/g;
+       $name =~ s/fsck_minix/fsck.minix/g;
+       $name =~ s/mkfs_minix/mkfs.minix/g;
+       $name =~ s/run_parts/run-parts/g;
+       $name =~ s/start_stop_daemon/start-stop-daemon/g;
+
+       # make options bold
+       my $trivial = $usage->{trivial};
+       if (!defined $usage->{trivial}) {
+               $trivial = "";
+       } else {
+               $trivial =~ s/(?<!\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..e0a7597
--- /dev/null
@@ -0,0 +1,1143 @@
+<!--#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>
+<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></li>
+  <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><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></li>
+  <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><a href="#who">Who are the BusyBox developers?</a></li>
+  </ul>
+</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:
+<pre>
+   make CROSS_COMPILE=arm-linux-uclibcgnueabi-
+</pre>
+   Alternatively CROSS_COMPILE can be set in the environment.
+   Default value for CROSS_COMPILE is not to prefix executables.
+</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 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>
+
+<p> If you prefer to deal with an organization rather than an individual, Rob
+    Landley (the current BusyBox maintainer) works for
+    <a http://www.timesys.com>TimeSys</a>, and Eric Andersen (the previous
+    busybox maintainer and current uClibc maintainer) owns
+    <a href="http://codepoet-consulting.com/">CodePoet Consulting</a>.  Both
+    companies offer support contracts and handle new development, and there
+    are plenty of other companies that do the same.
+</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="http://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?</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 0.9.26 there's a
+fairly large chance that whatever problem you're seeing has already been fixed.
+To get that fix, all you have to do is upgrade to a newer version.  If you
+don't at least _try_ that, you're wasting our time.</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 "linux" or "asm" directories of /usr/include contain Linux kernel
+headers, so that the C library can talk directly to the Linux kernel.  In
+a perfect world, applications shouldn't include these headers directly, but
+we don't live in a perfect world.</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 <linux/posix_types.h> 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
+<linux/version.h> 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 "my_dev_t.h", which #includes <linux/posix_types.h> and
+<linux/version.h> 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 Fischer
+andersen  :Erik Andersen      - uClibc and BuildRoot maintainer.
+bug1      :Glenn McGrath
+davidm    :David McCullough
+gkajmowi  :Garrett Kajmowicz  - uClibc++ maintainer
+jbglaw    :Jan-Benedict Glaw
+jocke     :Joakim Tjernlund
+landley   :Rob Landley        - BusyBox maintainer
+lethal    :Paul Mundt
+mjn3      :Manuel Novoa III
+osuadmin  :osuadmin
+pgf       :Paul Fox
+pkj       :Peter Kjellerstedt
+prpplague :David Anders
+psm       :Peter S. Mazinger
+russ      :Russ Dill
+sandman   :Robert Griebl
+sjhill    :Steven J. Hill
+solar     :Ned Ludd
+timr      :Tim Riker
+tobiasa   :Tobias Anderberg
+vapier    :Mike Frysinger
+</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 <robwen@gmail.com>
+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..475dd80
--- /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">Denis 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..0e97acd
--- /dev/null
@@ -0,0 +1,57 @@
+<!--#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.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="/cgi-bin/viewcvs.cgi/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..45621cd
--- /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..5f2335a
--- /dev/null
@@ -0,0 +1,47 @@
+<!-- Footer -->
+
+
+    </td>
+    </tr>
+    </table>
+
+<hr />
+
+
+    <table width="100%">
+       <tr>
+           <td width="60%">
+               <font face="arial, helvetica, sans-serif" size="-1">
+                   <a href="/copyright.txt">Copyright &copy; 1999-2005 Erik Andersen</a>
+                   <br>
+                   Mail all comments, insults, suggestions and bribes to
+                   <br>
+                   Denis Vlasenko <a href="mailto:vda.linux@googlemail.com">vda.linux@googlemail.com</a><br>
+               </font>
+           </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..60c6481
--- /dev/null
@@ -0,0 +1,101 @@
+<!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;
+     }
+     :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="/cgi-bin/viewcvs.cgi/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="http://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">Denis</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>
+
+<!--
+    <a href="http://validator.w3.org/check/referer"><img
+     src="/images/vh40.gif" height=31 width=88
+          align=left border=0 alt="Valid HTML 4.0!"></a>
+-->
+
+</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/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..76358bc
--- /dev/null
@@ -0,0 +1,97 @@
+<!--#include file="header.html" -->
+
+<p>
+<h3>BusyBox is licensed under the GNU General Public License, version 2</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 note on GPL versions</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 "GPL version 2 or (at your option)
+later versions" boilerplate permission grant.  Ancient versions of BusyBox
+(before svn 49) did not specify any version at all, and section 9 of GPLv2
+(the most recent version at the time) says those old versions may be
+redistributed under any version of GPL (including the obsolete V1).  This was
+conceptually similar to a dual license, except that the different licenses were
+different versions of the GPL.</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
+"may be redistributed as per the Linux copyright" (which Linus clarified in the
+2.4.0-pre8 release announcement in 2000 was GPLv2 only), and Linux kernel code
+copied into libbb/loop.c (after Linus's announcement).  There are probably
+more, because all we used to check was that the code was GPL, not which
+version.  (Before the GPLv3 draft proceedings in 2006, it was a purely
+theoretical issue that didn't come up much.)</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>License enforcement</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
+"accepts primary responsibility for enforcement of US copyrights on the
+software... and coordinates international copyright enforcement efforts for
+such works as necessary."  If you distribute BusyBox in a way that doesn't
+comply with the terms of the license BusyBox is distributed under, expect to
+hear from these guys.  Their entire reason for existing is to do pro-bono
+legal work for free/open source software projects.  (We used to list people who
+violate the BusyBox license in <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 Good Example</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&childpagename=US%2FLayout&cid=1115416836002&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..9cdbd7c
--- /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..3a28cc0
--- /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://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://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..c9f6829
--- /dev/null
@@ -0,0 +1,989 @@
+<!--#include file="header.html" -->
+
+<ul>
+  <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://busybox.net/cgi-bin/viewcvs.cgi/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>Made NOMMU-compatible: crond, crontab, ifupdown, inetd, init, runsv, svlogd, tcpsvd, udpsvd.
+
+    <p>Changes since previous release:
+      <ul>
+       <li>globally: add -Wunused-parameter
+       <li>globally: add optimization barrier to all "G trick" locations
+       <li>adduser/addgroup: check username for invalid chars (by Tito &lt;farmatito AT tiscali.it&gt;)
+       <li>adduser: optional support for long options. Closes bug 2134
+       <li>ash: handle "A=1 A=2 B=$A; echo $B". Closes bug 947
+       <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>build system: don't use "gcc -o /dev/null", old gcc can delete /dev/null in this case
+       <li>build system: fixes for cross-compiling on an OS X host
+       <li>build system: make it do without "od -t"
+       <li>build system: pass CFLAGS to link stage too. Closes bug 1376
+       <li>build system: add CONFIG_NOMMU
+       <li>cp: add ENABLE_FEATURE_VERBOSE_CP_MESSAGE. Closes bug 1470
+       <li>crontab: almost complete rewrite
+       <li>dnsd: properly set _src_ IP:port on outgoing UDP packets
+       <li>dpkg: fix bug where existence check was reversed
+       <li>eject: add -s for SCSI- and USB-devices (Nico Erfurth)
+       <li>fdisk: fix a case where break was reached only for DOS labels
+       <li>fsck: don't kill pid -1! (Roy Marples &lt;roy at marples.name&gt;)
+       <li>fsck_minix: fix bug in map_block2: s/(blknr &gt;= 256 * 256)/(blknr &lt; 256 * 256)/
+       <li>fuser: substantial rewrite
+       <li>getopt: add support for "a+" specifier for nonnegative int parameters. By Vladimir Dronnikov &lt;dronnikov at gmail.com&gt;
+       <li>getty: don't try to detect parity on local lines (Joakim Tjernlund &lt;Joakim.Tjernlund at transmode.se&gt;)
+       <li>halt: write wtmp entry if wtmp support is enabled
+       <li>httpd: "HEAD" support. Closes bug 1530
+       <li>httpd: fix bug 2004: wrong argv when interpreter is invoked
+       <li>httpd: fix bug where we did chdir("") if CGI path had only one "/"
+       <li>httpd: fix for POST upload
+       <li>httpd: support for "I:index.xml" syntax (Peter Korsgaard &lt;jacmet AT uclibc.org&gt;)
+       <li>hush: fix a case where none of pipe members could be started because of fork failure
+       <li>hush: more correct handling of piping
+       <li>hush: reinstate `cmd` handling for NOMMU
+       <li>hush: report [v]fork failures
+       <li>hush: set CLOEXEC on script file being executed
+       <li>hush: try to add a bit more of vfork-friendliness
+       <li>inetd: make "udp nowait" work
+       <li>inetd: make inetd IPv6-capable
+       <li>init: add FEATURE_KILL_REMOVED (Eugene Bordenkircher &lt;eugebo AT gmail.com&gt;)
+       <li>init: allow last line of config file to be not terminated by "\n"
+       <li>init: do not die if "/dev/null" is missing
+       <li>init: fix bug 1111: restart actions were not splitting words
+       <li>init: wait for orphaned children too while waiting for sysinit-like processes (harald-tuxbox AT arcor.de)
+       <li>ip route: "ip route" was misbehaving (extra argv+1 ate 1st env var)
+       <li>last: do not go into endless loop on read error
+       <li>less,klogd,syslogd,nc,tcpudp: exit on signal by killing itself, not exit(1)
+       <li>less: "examine" command will not bomb out on bad file name now
+       <li>less: fix bug where backspace wasn't actually deleting chars
+       <li>less: make it a bit more resistant against status line corruption
+       <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>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>libbb: filename completion matches dangling symlinks too
+       <li>libbb: fix getopt state corruption for NOFORK applets
+       <li>libbb: full_read/write now will report partial data counts prior to error
+       <li>libbb: intrduce and use safe_gethostname. By Tito &lt;farmatito AT tiscali.it&gt;
+       <li>libbb: introduce and use nonblock_safe_read(). Yay! Our shells are immune from this nasty O_NONBLOCK now!
+       <li>login,su: avoid clearing environment with some options, as was intended
+       <li>microcom: read more than 1 byte from device, if possible
+       <li>microcom: split -d (delay) option away from -t
+       <li>mktemp: support -p DIR (Timo Teras &lt;timo.teras at iki.fi&gt;)
+       <li>mount: #ifdef out MOUNT_LABEL code parts if it is not selected
+       <li>mount: add another mount helper call method
+       <li>mount: allow and ignore _netdev option
+       <li>mount: make -f work even without mtab support (Cristian Ionescu-Idbohrn &lt;cristian.ionescu-idbohrn at axis.com&gt;)
+       <li>mount: optional support for -vv verbosity
+       <li>mount: plug a hole where FEATURE_MOUNT_HELPERS could allow execution of arbitrary command
+       <li>mount: recognize "dirsync" (closes bug 835)
+       <li>mount: sanitize environment if called by non-root
+       <li>mount: support for mount by label. Closes bug 1143
+       <li>mount: with -vv -f, say what mount() calls we were going to make
+       <li>msh: create testsuite (based on hush one)
+       <li>msh: don't use floating point in "times" builtin
+       <li>msh: fix Ctrl-C handling with line editing
+       <li>msh: fix for bug 846 ("break" didn't work second time)
+       <li>msh: glob0/glob1/glob2/glob3 were just a sorting routine, removed
+       <li>msh: instead of fixing "ls | cd", "cd | ls" etc disallow builtins in pipes. They make no sense there anyway
+       <li>msh: stop trying to parse variables in "msh SCRIPT VAR=val param". They are passed as ordinary parameters
+       <li>netstat: print control chars as "^C" etc
+       <li>nmeter: fix bug where %[mf] behaves as %[mt]
+       <li>nohup: compat patch by Christoph Gysin &lt;mailinglist.cache at gmail.com&gt;
+       <li>od: handle /proc files (which have filesize 0) correctly
+       <li>patch: don't trash permissions of patched file
+       <li>ps: add conditional support for -o [e]time
+       <li>ps: fix COMMAND column adjustment; overflow in USER and VSZ columns
+       <li>reset: call "stty sane". Closes bug 1414
+       <li>rmdir: optional long options support for Debian users. By Roberto Gordo Saez &lt;roberto.gordo AT gmail.com&gt;
+       <li>run-parts: add --reverse
+       <li>script: correctly handle buffered "tail" of output
+       <li>sed: "n" command must reset "we had successful subst" flag. Closes bug 1214
+       <li>sort: -z outputs NUL terminated lines. Closes bug 1591
+       <li>stty: fix mishandling of control keywords (Ralf Friedl &lt;Ralf.Friedl AT online.de&gt;)
+       <li>switch_root: stop at first non-option. Closes bug 1425
+       <li>syslogd: avoid excessive time() system calls
+       <li>syslogd: don't die if remote host's IP cannot be resolved. Retry resolutions every two minutes instead
+       <li>syslogd: fix shmat error check
+       <li>syslogd: optional support for dropping dups. Closes bug 436
+       <li>syslogd: send "\n"-terminated messages over the network. Fully closes bug 1574
+       <li>syslogd: tighten up hostname handling
+       <li>tail: fix "tail -c 20 /dev/huge_disk" (was taking ages)
+       <li>tar: compat: handle tarballs with only one zero block at the end
+       <li>tar: autodetection of gz/bz2 compressed tarballs. Closes bug 992
+       <li>tar: real support for -p. By Natanael Copa &lt;natanael.copa at gmail.com&gt; 
+       <li>tcpudp: narrow down time window where we have no wildcard socket
+       <li>telnetd: use login always, not "sometimes login, sometimes shell"
+       <li>test: fix mishandling of "test ! arg1 op arg2 more args"
+       <li>trylink: instead of build error, disable --gc-sections if GLIBC and STATIC are selected
+       <li>udhcp: make file paths configurable
+       <li>udhcp: optional support for non-standard DHCP ports
+       <li>udhcp: set correct op byte in the packet for DHCPDECLINE
+       <li>udhcpc: filter unwanted packets in kernel (Cristian Ionescu-Idbohrn &lt;cristian.ionescu-idbohrn AT axis.com&gt;)
+       <li>udhcpc: fix wrong options in decline and release packets (Jonas Danielsson &lt;jonas.danielsson AT axis.com&gt;)
+       <li>umount: do not complain several times about the same mountpoint
+       <li>umount: do not try to free loop device or erase mtab if remounted ro
+       <li>umount: instead of non-standard -D, use -d with opposite meaning. Closes bug 1604
+       <li>unlzma: shrink by Pascal Bellard &lt;pascal.bellard AT ads-lu.com&gt;
+       <li>unzip: do not try to read entire compressed stream at once (it can be huge)
+       <li>unzip: handle short reads correctly
+       <li>vi: many fixes
+       <li>zcip: don't chdir to root
+       <li>zcip: open ARP socket before openlog (else we can thrash syslog socket)
+      </ul>
+    </p>
+
+  <li><b>21 March 2008 -- BusyBox 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>
+    <a href=http://busybox.net/~vda/HOWTO_bbox_with_uclibc.txt>How to build static busybox against uclibc</a>
+    <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><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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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>
+    </p>
+
+  <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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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>
+    </p>
+
+  </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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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>
+    </p>
+  </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://busybox.net/cgi-bin/viewcvs.cgi/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://busybox.net/cgi-bin/viewcvs.cgi/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>
+    </p>
+  </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>
+    </p>
+  </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>
+    </p>
+  </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>Old News</b><p>
+    <a href="/oldnews.html">Click here to read 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..1017b69
--- /dev/null
@@ -0,0 +1,1140 @@
+<!--#include file="header.html" -->
+
+
+<ul>
+  <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="http://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="http://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" border="0" 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="/cgi-bin/viewcvs.cgi/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..a727d9f
--- /dev/null
@@ -0,0 +1,170 @@
+<!--#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 products and/or 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 system systems.
+
+</li><li><a href=
+"http://cvs.debian.org/boot-floppies/">
+Debian installer (boot floppies) project</a>
+
+</li><li><a href="http://redhat.com/">Red Hat installer</a>
+
+</li><li><a href=
+"http://distro.ibiblio.org/pub/Linux/distributions/slackware/slackware-current/source/rootdisks/">
+Slackware Installer</a>
+
+</li><li><a href="http://www.gentoo.org/">Gentoo Linux install/boot CDs</a>
+</li><li><a href="http://www.mandriva.com/">The Mandriva installer</a>
+
+</li><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><li><a href=
+"http://www.toms.net/rb/">tomsrtbt</a>
+
+</li><li><a href="http://www.stormix.com/">Stormix
+Installer</a>
+
+</li><li><a href=
+"http://www.emacinc.com/linux2_sbc.htm">EMAC Linux
+2.0 SBC</a>
+
+</li><li><a href="http://www.trinux.org/">Trinux</a>
+
+</li><li><a href="http://oddas.sourceforge.net/">ODDAS
+project</a>
+
+</li><li><a href="http://byld.sourceforge.net/">Build Your
+Linux Disk</a>
+
+</li><li><a href=
+"http://ibiblio.org/pub/Linux/system/recovery">Zdisk</a>
+
+</li><li><a href="http://www.adtran.com">AdTran -
+VPN/firewall VPN Linux Distribution</a>
+
+</li><li><a href="http://mkcdrec.ota.be/">mkCDrec - make
+CD-ROM recovery</a>
+
+</li><li><a href=
+"http://recycle.lbl.gov/~ldoolitt/bse/">Linux on
+nanoEngine</a>
+
+</li><li><a href=
+"http://www.zelow.no/floppyfw/">Floppyfw</a>
+
+</li><li><a href="http://www.ltsp.org/">Linux Terminal
+Server Project</a>
+
+</li><li><a href="http://www.devil-linux.org/">Devil-Linux</a>
+
+</li><li><a href="http://dutnux.sourceforge.net/">DutNux</a>
+
+</li><li><a href="http://www.microwerks.net/~hugo/mindi/">Mindi</a>
+
+</li><li><a href="http://www.minimalinux.org/ttylinux/">ttylinux</a>
+
+</li><li><a href="http://www.coyotelinux.com/">Coyote Linux</a>
+
+</li><li><a href="http://www.partimage.org/">Partition
+Image</a>
+
+</li><li><a href="http://www.fli4l.de/">fli4l the on(e)-disk-router</a>
+
+</li><li><a href="http://tinfoilhat.cultists.net/">Tinfoil
+Hat Linux</a>
+
+</li><li><a href="http://sourceforge.net/projects/gp32linux/">gp32linux</a>
+</li><li><a href="http://familiar.handhelds.org/">Familiar Linux</a><br>A linux distribution for handheld computers
+</li><li><a href="http://rescuecd.sourceforge.net/">Timo's Rescue CD Set</a>
+</li><li><a href="http://sf.net/projects/netstation/">Netstation</a>
+</li><li><a href="http://www.fiwix.org/">GNU/Fiwix Operating System</a>
+</li><li><a href="http://www.softcraft.com/">Generations Linux</a>
+</li><li><a href="http://systemimager.org/relatedprojects/">SystemImager / System Installation Suite</a>
+</li><li><a href="http://www.bablokb.de/gendist/">GENDIST distribution generator</a>
+</li><li><a href="http://diet-pc.sourceforge.net/">DIET-PC embedded Linux thin client distribution</a>
+</li><li><a href="http://byzgl.sourceforge.net/">BYZantine Gnu/Linux</a>
+</li><li><a href="http://dban.sourceforge.net/">Darik's Boot and Nuke</a>
+</li><li><a href="http://www.timesys.com/">TimeSys real-time Linux</a>
+</li><li><a href="http://movix.sf.net/">MoviX</a><br>Boots from CD and automatically plays every video file on the CD
+</li><li><a href="http://katamaran.sourceforge.net">katamaran</a><br>Linux, X11, xfce windowmanager, based on BusyBox
+</li><li><a href="http://www.sourceforge.net/projects/simplygnustep">Prometheus SimplyGNUstep</a>
+</li><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><li><a href="http://metadistros.hispalinux.es/">Metadistros</a><br>a project to allow you easily make Live-CD distributions.
+</li><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><li><a href="http://www.stresslinux.org/">stresslinux</a><br>minimal linux distribution running from a bootable cdrom or via PXE.
+</li><li><a href="http://thinstation.sourceforge.net/">thinstation</a><br>convert standard PCs into full-featured diskless thinclients.
+</li><li><a href="http://www.uhulinux.hu/">UHU-Linux Hungary</a>
+</li><li><a href="http://deep-water.berlios.de/">Deep-Water Linux</a>
+</li><li><a href="http://www.freesco.org/">Freesco router</a>
+</li><li><a href="http://Sentry.SourceForge.net/">Sentry Firewall CD</a>
+
+
+
+</li><li><a href="http://tuxscreen.net">Tuxscreen Linux Phone</a>
+</li><li><a href="http://www.kerbango.com/">The Kerbango Internet Radio</a>
+</li><li><a href="http://www.linuxmagic.com/vpn/">LinuxMagic VPN Firewall</a>
+</li><li><a href="http://www.isilver-inc.com/">I-Silver Linux appliance servers</a>
+</li><li><a href="http://zaurus.sourceforge.net/">Sharp Zaurus PDA</a>
+</li><li><a href="http://www.cyclades.com/">Cyclades-TS and other Cyclades products</a>
+</li><li><a href="http://www.linksys.com/products/product.asp?prid=508">Linksys WRT54G - Wireless-G Broadband Router</a>
+</li><li><a href="http://www.dell.com/us/en/biz/topics/sbtopic_005_truemobile.htm">Dell TrueMobile 1184</a>
+</li><li><a href="http://actiontec.com/products/modems/dual_pcmodem/dpm_overview.html">Actiontec Dual PC Modem</a>
+</li><li><a href="http://www.kiss-technology.com/">Kiss DP Series DVD players</a>
+</li><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><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><li><a href="http://www.buffalo-technology.com/webcontent/products/wireless/wbr-g54.htm">Buffalo WBR-G54 wireless router</a>
+  </li><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><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><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><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://128.121.226.214/gtproducts/index.html">here</a>
+</li><li><a href="http://smartlinux.sourceforge.net/">S.M.A.R.T. Linux</a>
+</li><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><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><li><a href="http://freeterm.spb.ru/frwt/">Free Remote Windows Terminal</a>
+
+</li><li><a href="http://www.zyxel.com/">ZyXEL Routers</a>
+
+</li>
+</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..9d821da
--- /dev/null
@@ -0,0 +1,66 @@
+<!--#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.8.0 (2007-11-04 15:42:38 GMT) multi-call binary
+Copyright (C) 1998-2006 Erik Andersen, Rob Landley, 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, bunzip2, bzcat, bzip2, cal, cat, catv, chattr,
+        chgrp, chmod, chown, chpasswd, chpst, chroot, chrt, chvt,
+        cksum, clear, cmp, comm, cp, cpio, crond, crontab, cryptpw,
+        cut, date, dc, dd, deallocvt, delgroup, deluser, df, dhcprelay,
+        diff, dirname, dmesg, dnsd, dos2unix, dpkg, du, dumpkmap,
+        dumpleases, echo, ed, egrep, eject, env, envdir, envuidgid,
+        expand, expr, fakeidentd, false, fbset, fdflush, fdformat,
+        fdisk, fgrep, find, fold, free, freeramdisk, fsck, fsck.minix,
+        ftpget, ftpput, fuser, getopt, getty, grep, gunzip, gzip,
+        hdparm, head, hexdump, hostid, hostname, httpd, hush, hwclock,
+        id, ifconfig, inetd, insmod, install, ip, ipaddr, ipcalc,
+        ipcrm, ipcs, iplink, iproute, iprule, iptunnel, kbd_mode,
+        kill, killall, killall5, klogd, lash, last, length, less,
+        linux32, linux64, ln, loadfont, loadkmap, logger, login, logname,
+        logread, losetup, ls, lsattr, lsmod, lzmacat, 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, printenv, printf, ps, pscan, pwd, raidautorun, rdate,
+        readlink, readprofile, realpath, renice, reset, resize, rm,
+        rmdir, rmmod, route, rpm, rpm2cpio, run-parts, runlevel, runsv,
+        runsvdir, rx, sed, seq, setarch, setconsole, setkeycodes,
+        setlogcons, 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, tail, tar, tcpsvd, tee, telnet, telnetd, test, tftp,
+        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..ba7920b
--- /dev/null
@@ -0,0 +1,35 @@
+<!--#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.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>
+
+  <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.timesys.com">TimeSys</a><br>
+  Embedded Linux development, cross-compilers, real-time, KGDB, tsrpm and cygwin.
+  </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:rob@landley.net">Rob</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..561a5b8
--- /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 current <em>stable branch</em> can be obtained with
+<pre>
+svn co svn://busybox.net/branches/busybox_1_9_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/tinyutils.html b/docs/busybox.net/tinyutils.html
new file mode 100644 (file)
index 0000000..9122d6e
--- /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 border=1>
+<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 border=1>
+<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</en>
+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..804b839
--- /dev/null
@@ -0,0 +1,82 @@
+# vi: set sw=4 ts=4:
+
+=head1 NAME
+
+BusyBox - The Swiss Army Knife of Embedded Linux
+
+=head1 SYNTAX
+
+ BusyBox <function> [arguments...]  # or
+
+ <function> [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 commands support the B<--help> argument to provide a terse runtime
+description of their behavior.  If the CONFIG_FEATURE_VERBOSE_USAGE option has
+been enabled, more detailed usage information will also be available.
+
+=head1 COMMANDS
+
+Currently defined functions include:
+
diff --git a/docs/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/mdev.txt b/docs/mdev.txt
new file mode 100644 (file)
index 0000000..38f1da9
--- /dev/null
@@ -0,0 +1,91 @@
+-------------
+ 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:
+[1] mount -t sysfs sysfs /sys
+[2] echo /bin/mdev > /proc/sys/kernel/hotplug
+[3] mdev -s
+
+Of course, a more "full" setup would entail executing this before the previous
+code snippet:
+[4] mount -t tmpfs mdev /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>
+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/relocate 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 relocate "hda" into the drives/ subdirectory.
+       hdb 0:3 660 >cdrom
+This will rename "hdb" to "cdrom".
+
+If you also enable support for executing your own commands, then the file has
+the format:
+       <device regex> <uid>:<gid> <octal permissions> [<@|$|*> <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 want to know what to name the file in
+userspace.
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..fe8d031
--- /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..e783d3e
--- /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 ATTRIBUTE_UNUSED, 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..b3ea3ae
--- /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
+
+/*
+ * 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 /* _LINUX_EXT2_FS_H */
diff --git a/e2fsprogs/e2fs_lib.c b/e2fsprogs/e2fs_lib.c
new file mode 100644 (file)
index 0000000..89e0500
--- /dev/null
@@ -0,0 +1,227 @@
+/* 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
+ */
+
+#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_flags(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..d01249d
--- /dev/null
@@ -0,0 +1,43 @@
+/* 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"
+
+/* 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_flags(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
diff --git a/e2fsprogs/fsck.c b/e2fsprogs/fsck.c
new file mode 100644 (file)
index 0000000..178792f
--- /dev/null
@@ -0,0 +1,1187 @@
+/* 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.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/* 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);
+       fs->type = xstrdup(type);
+       fs->opts = xstrdup(opts ? opts : "");
+       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 void strip_line(char *line)
+{
+       char *p = line + strlen(line) - 1;
+
+       while (*line) {
+               if (*p != '\n' && *p != '\r')
+                       break;
+               *p-- = '\0';
+       }
+}
+
+static char *parse_word(char **buf)
+{
+       char *word, *next;
+
+       word = *buf;
+       if (*word == '\0')
+               return NULL;
+
+       word = skip_whitespace(word);
+       next = skip_non_whitespace(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 int parse_fstab_line(char *line, struct fs_info **ret_fs)
+{
+       char *device, *mntpnt, *type, *opts, *passno, *cp;
+       struct fs_info *fs;
+
+       *ret_fs = NULL;
+       strip_line(line);
+       *strchrnul(line, '#') = '\0'; /* Ignore everything after comment */
+       cp = line;
+
+       device = parse_word(&cp);
+       if (!device) return 0; /* Allow blank lines */
+       mntpnt = parse_word(&cp);
+       type = parse_word(&cp);
+       opts = parse_word(&cp);
+       /*freq =*/ parse_word(&cp);
+       passno = parse_word(&cp);
+
+       if (!mntpnt || !type)
+               return -1;
+
+       parse_escape(device);
+       parse_escape(mntpnt);
+       parse_escape(type);
+       parse_escape(opts);
+       parse_escape(passno);
+
+       if (strchr(type, ','))
+               type = NULL;
+
+       fs = create_fs_device(device, mntpnt, type ? type : "auto", opts,
+                       (passno ? atoi(passno) : -1));
+       *ret_fs = fs;
+       return 0;
+}
+
+/* Load the filesystem database from /etc/fstab */
+static void load_fs_info(const char *filename)
+{
+       FILE *f;
+       int lineno = 0;
+       int old_fstab = 1;
+       struct fs_info *fs;
+
+       f = fopen_or_warn(filename, "r");
+       if (f == NULL) {
+               return;
+       }
+       while (1) {
+               int r;
+               char *buf = xmalloc_getline(f);
+               if (!buf) break;
+               r = parse_fstab_line(buf, &fs);
+               free(buf);
+               lineno++;
+               if (r < 0) {
+                       bb_error_msg("WARNING: bad format "
+                               "on line %d of %s", lineno, filename);
+                       continue;
+               }
+               if (!fs)
+                       continue;
+               if (fs->passno < 0)
+                       fs->passno = 0;
+               else
+                       old_fstab = 0;
+       }
+       fclose(f);
+
+       if (old_fstab) {
+               fputs("\007"
+"WARNING: Your /etc/fstab does not contain the fsck passno field.\n"
+"I will kludge around things for you, but you should fix\n"
+"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;
+
+       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(devices, (num_devices+1) * sizeof(devices[0]));
+                       devices[num_devices++] = xstrdup(arg);
+                       continue;
+               }
+
+               if (arg[0] != '-' || opts_for_fsck) {
+                       args = xrealloc(args, (num_args+1) * sizeof(args[0]));
+                       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(args, (num_args+1) * sizeof(args[0]));
+                       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 ATTRIBUTE_UNUSED)
+{
+       cancel_requested = 1;
+}
+
+int fsck_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fsck_main(int argc ATTRIBUTE_UNUSED, 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..13eeb35
--- /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_flags(stdout, fsflags, PFOPT_LONG);
+               bb_putchar('\n');
+       } else {
+               print_flags(stdout, fsflags, 0);
+               printf(" %s\n", name);
+       }
+
+       return;
+ read_err:
+       bb_perror_msg("reading %s", name);
+}
+
+static int lsattr_dir_proc(const char *dir_name, struct dirent *de,
+                          void *private ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, 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..0062b2f
--- /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..4fa9f6f
--- /dev/null
@@ -0,0 +1,105 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * blkid.h - Interface for libblkid, a library to identify block devices
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#ifndef _BLKID_BLKID_H
+#define _BLKID_BLKID_H
+
+#include <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 /* _BLKID_BLKID_H */
diff --git a/e2fsprogs/old_e2fsprogs/blkid/blkidP.h b/e2fsprogs/old_e2fsprogs/blkid/blkidP.h
new file mode 100644 (file)
index 0000000..c7cb8ab
--- /dev/null
@@ -0,0 +1,187 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * blkidP.h - Internal interfaces for libblkid
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#ifndef _BLKID_BLKIDP_H
+#define _BLKID_BLKIDP_H
+
+#include <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 /* _BLKID_BLKIDP_H */
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..071aa5a
--- /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(lvm_device, "r")) == NULL) {
+               DBG(DEBUG_DEVNAME, printf("%s: (%d) %s\n", lvm_device, errno,
+                                         strerror(errno)));
+               return 0;
+       }
+
+       while (fgets(buf, sizeof(buf), lvf)) {
+               if (sscanf(buf, "device: %d:%d", &ma, &mi) == 2) {
+                       ret = makedev(ma, mi);
+                       break;
+               }
+       }
+       fclose(lvf);
+
+       return ret;
+}
+
+static void lvm_probe_all(blkid_cache cache)
+{
+       DIR             *vg_list;
+       struct dirent   *vg_iter;
+       int             vg_len = strlen(VG_DIR);
+       dev_t           dev;
+
+       if ((vg_list = opendir(VG_DIR)) == NULL)
+               return;
+
+       DBG(DEBUG_DEVNAME, printf("probing LVM devices under %s\n", VG_DIR));
+
+       while ((vg_iter = readdir(vg_list)) != NULL) {
+               DIR             *lv_list;
+               char            *vdirname;
+               char            *vg_name;
+               struct dirent   *lv_iter;
+
+               vg_name = vg_iter->d_name;
+               if (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(PROC_EVMS_VOLUMES, "r");
+       if (!procpt)
+               return 0;
+       while (fgets(line, sizeof(line), procpt)) {
+               if (sscanf (line, " %d %d %d %*s %*s %[^\n ]",
+                           &ma, &mi, &sz, device) != 4)
+                       continue;
+
+               DBG(DEBUG_DEVNAME, printf("Checking partition %s (%d, %d)\n",
+                                         device, ma, mi));
+
+               probe_one(cache, device, makedev(ma, mi), BLKID_PRI_EVMS);
+               num++;
+       }
+       fclose(procpt);
+       return num;
+}
+
+/*
+ * Read the device data for all available block devices in the system.
+ */
+int blkid_probe_all(blkid_cache cache)
+{
+       FILE *proc;
+       char line[1024];
+       char ptname0[128], ptname1[128], *ptname = 0;
+       char *ptnames[2];
+       dev_t devs[2];
+       int ma, mi;
+       unsigned long long sz;
+       int lens[2] = { 0, 0 };
+       int which = 0, last = 0;
+
+       ptnames[0] = ptname0;
+       ptnames[1] = ptname1;
+
+       if (!cache)
+               return -BLKID_ERR_PARAM;
+
+       if (cache->bic_flags & BLKID_BIC_FL_PROBED &&
+           time(0) - cache->bic_time < BLKID_PROBE_INTERVAL)
+               return 0;
+
+       blkid_read_cache(cache);
+       evms_probe_all(cache);
+#ifdef VG_DIR
+       lvm_probe_all(cache);
+#endif
+
+       proc = fopen(PROC_PARTITIONS, "r");
+       if (!proc)
+               return -BLKID_ERR_PROC;
+
+       while (fgets(line, sizeof(line), proc)) {
+               last = which;
+               which ^= 1;
+               ptname = ptnames[which];
+
+               if (sscanf(line, " %d %d %llu %128[^\n ]",
+                          &ma, &mi, &sz, ptname) != 4)
+                       continue;
+               devs[which] = makedev(ma, mi);
+
+               DBG(DEBUG_DEVNAME, printf("read partition name %s\n", ptname));
+
+               /* Skip whole disk devs unless they have no partitions
+                * If we don't have a partition on this dev, also
+                * check previous dev to see if it didn't have a partn.
+                * heuristic: partition name ends in a digit.
+                *
+                * Skip extended partitions.
+                * heuristic: size is 1
+                *
+                * FIXME: skip /dev/{ida,cciss,rd} whole-disk devs
+                */
+
+               lens[which] = strlen(ptname);
+               if (isdigit(ptname[lens[which] - 1])) {
+                       DBG(DEBUG_DEVNAME,
+                           printf("partition dev %s, devno 0x%04X\n",
+                                  ptname, (unsigned int) devs[which]));
+
+                       if (sz > 1)
+                               probe_one(cache, ptname, devs[which], 0);
+                       lens[which] = 0;
+                       lens[last] = 0;
+               } else if (lens[last] && strncmp(ptnames[last], ptname,
+                                                lens[last])) {
+                       DBG(DEBUG_DEVNAME,
+                           printf("whole dev %s, devno 0x%04X\n",
+                                  ptnames[last], (unsigned int) devs[last]));
+                       probe_one(cache, ptnames[last], devs[last], 0);
+                       lens[last] = 0;
+               }
+       }
+
+       /* Handle the last device if it wasn't partitioned */
+       if (lens[which])
+               probe_one(cache, ptname, devs[which], 0);
+
+       fclose(proc);
+
+       cache->bic_time = time(0);
+       cache->bic_flags |= BLKID_BIC_FL_PROBED;
+       blkid_flush_cache(cache);
+       return 0;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+       blkid_cache cache = NULL;
+       int ret;
+
+       blkid_debug_mask = DEBUG_ALL;
+       if (argc != 1) {
+               fprintf(stderr, "Usage: %s\n"
+                       "Probe all devices and exit\n", argv[0]);
+               exit(1);
+       }
+       if ((ret = blkid_get_cache(&cache, bb_dev_null)) != 0) {
+               fprintf(stderr, "%s: error creating cache (%d)\n",
+                       argv[0], ret);
+               exit(1);
+       }
+       if (blkid_probe_all(cache) < 0)
+               printf("%s: error probing devices\n", argv[0]);
+
+       blkid_put_cache(cache);
+       return 0;
+}
+#endif
diff --git a/e2fsprogs/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..8b06d85
--- /dev/null
@@ -0,0 +1,73 @@
+/* vi: set sw=4 ts=4: */
+#if !defined(_BLKID_LIST_H) && !defined(LIST_HEAD)
+#define _BLKID_LIST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+       struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+void __list_add(struct list_head * add, struct list_head * prev,       struct list_head * next);
+void list_add(struct list_head *add, struct list_head *head);
+void list_add_tail(struct list_head *add, struct list_head *head);
+void __list_del(struct list_head * prev, struct list_head * next);
+void list_del(struct list_head *entry);
+void list_del_init(struct list_head *entry);
+int list_empty(struct list_head *head);
+void list_splice(struct list_head *list, struct list_head *head);
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:       the &struct list_head pointer.
+ * @type:      the type of the struct this is embedded in.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+       ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
+
+/**
+ * list_for_each - iterate over elements in a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_safe - iterate over elements in a list, but don't dereference
+ *                      pos after the body is done (in case it is freed)
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @pnext:     the &struct list_head to use as a pointer to the next item.
+ * @head:      the head for your list (not included in iteration).
+ */
+#define list_for_each_safe(pos, pnext, head) \
+       for (pos = (head)->next, pnext = pos->next; pos != (head); \
+            pos = pnext, pnext = pos->next)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BLKID_LIST_H */
diff --git a/e2fsprogs/old_e2fsprogs/blkid/probe.c b/e2fsprogs/old_e2fsprogs/blkid/probe.c
new file mode 100644 (file)
index 0000000..453b4d0
--- /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(0);
+       diff = now - dev->bid_time;
+
+       if ((now < dev->bid_time) ||
+           (diff < BLKID_PROBE_MIN) ||
+           (dev->bid_flags & BLKID_BID_FL_VERIFIED &&
+            diff < BLKID_PROBE_INTERVAL))
+               return dev;
+
+       DBG(DEBUG_PROBE,
+           printf("need to revalidate %s (time since last check %lu)\n",
+                  dev->bid_name, diff));
+
+       if (((fd = open(dev->bid_name, O_RDONLY)) < 0) ||
+           (fstat(fd, &st) < 0)) {
+               if (errno == ENXIO || errno == ENODEV || errno == ENOENT) {
+                       blkid_free_dev(dev);
+                       return NULL;
+               }
+               /* We don't have read permission, just return cache data. */
+               DBG(DEBUG_PROBE,
+                   printf("returning unverified data for %s\n",
+                          dev->bid_name));
+               return dev;
+       }
+
+       memset(bufs, 0, sizeof(bufs));
+
+       /*
+        * Iterate over the type array.  If we already know the type,
+        * then try that first.  If it doesn't work, then blow away
+        * the type information, and try again.
+        *
+        */
+try_again:
+       type = 0;
+       if (!dev->bid_type || !strcmp(dev->bid_type, "mdraid")) {
+               uuid_t  uuid;
+
+               if (check_mdraid(fd, uuid) == 0) {
+                       set_uuid(dev, uuid);
+                       type = "mdraid";
+                       goto found_type;
+               }
+       }
+       for (id = type_array; id->bim_type; id++) {
+               if (dev->bid_type &&
+                   strcmp(id->bim_type, dev->bid_type))
+                       continue;
+
+               idx = id->bim_kboff + (id->bim_sboff >> 10);
+               if (idx > BLKID_BLK_OFFS || idx < 0)
+                       continue;
+               buf = bufs[idx];
+               if (!buf) {
+                       if (lseek(fd, idx << 10, SEEK_SET) < 0)
+                               continue;
+
+                       buf = xmalloc(1024);
+
+                       if (read(fd, buf, 1024) != 1024) {
+                               free(buf);
+                               continue;
+                       }
+                       bufs[idx] = buf;
+               }
+
+               if (memcmp(id->bim_magic, buf + (id->bim_sboff&0x3ff),
+                          id->bim_len))
+                       continue;
+
+               if ((id->bim_probe == NULL) ||
+                   (id->bim_probe(fd, cache, dev, id, buf) == 0)) {
+                       type = id->bim_type;
+                       goto found_type;
+               }
+       }
+
+       if (!id->bim_type && dev->bid_type) {
+               /*
+                * Zap the device filesystem type and try again
+                */
+               blkid_set_tag(dev, "TYPE", 0, 0);
+               blkid_set_tag(dev, "SEC_TYPE", 0, 0);
+               blkid_set_tag(dev, "LABEL", 0, 0);
+               blkid_set_tag(dev, "UUID", 0, 0);
+               goto try_again;
+       }
+
+       if (!dev->bid_type) {
+               blkid_free_dev(dev);
+               return NULL;
+       }
+
+found_type:
+       if (dev && type) {
+               dev->bid_devno = st.st_rdev;
+               dev->bid_time = time(0);
+               dev->bid_flags |= BLKID_BID_FL_VERIFIED;
+               cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+
+               blkid_set_tag(dev, "TYPE", type, 0);
+
+               DBG(DEBUG_PROBE, printf("%s: devno 0x%04llx, type %s\n",
+                          dev->bid_name, st.st_rdev, type));
+       }
+
+       close(fd);
+
+       return dev;
+}
+
+int blkid_known_fstype(const char *fstype)
+{
+       const struct blkid_magic *id;
+
+       for (id = type_array; id->bim_type; id++) {
+               if (strcmp(fstype, id->bim_type) == 0)
+                       return 1;
+       }
+       return 0;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+       blkid_dev dev;
+       blkid_cache cache;
+       int ret;
+
+       blkid_debug_mask = DEBUG_ALL;
+       if (argc != 2) {
+               fprintf(stderr, "Usage: %s device\n"
+                       "Probe a single device to determine type\n", argv[0]);
+               exit(1);
+       }
+       if ((ret = blkid_get_cache(&cache, bb_dev_null)) != 0) {
+               fprintf(stderr, "%s: error creating cache (%d)\n",
+                       argv[0], ret);
+               exit(1);
+       }
+       dev = blkid_get_dev(cache, argv[1], BLKID_DEV_NORMAL);
+       if (!dev) {
+               printf("%s: %s has an unsupported type\n", argv[0], argv[1]);
+               return 1;
+       }
+       printf("%s is type %s\n", argv[1], dev->bid_type ?
+               dev->bid_type : "(null)");
+       if (dev->bid_label)
+               printf("\tlabel is '%s'\n", dev->bid_label);
+       if (dev->bid_uuid)
+               printf("\tuuid is %s\n", dev->bid_uuid);
+
+       blkid_free_dev(dev);
+       return 0;
+}
+#endif
diff --git a/e2fsprogs/old_e2fsprogs/blkid/probe.h b/e2fsprogs/old_e2fsprogs/blkid/probe.h
new file mode 100644 (file)
index 0000000..0fd16a7
--- /dev/null
@@ -0,0 +1,375 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * probe.h - constants and on-disk structures for extracting device data
+ *
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#ifndef _BLKID_PROBE_H
+#define _BLKID_PROBE_H
+
+#include <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..cdbaabc
--- /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(filename, "w");
+               opened = filename;
+       }
+
+       DBG(DEBUG_SAVE,
+           printf("writing cache file %s (really %s)\n",
+                  filename, opened));
+
+       if (!file) {
+               ret = errno;
+               goto errout;
+       }
+
+       list_for_each(p, &cache->bic_devs) {
+               blkid_dev dev = list_entry(p, struct blkid_struct_dev, bid_devs);
+               if (!dev->bid_type)
+                       continue;
+               if ((ret = save_dev(dev, file)) < 0)
+                       break;
+       }
+
+       if (ret >= 0) {
+               cache->bic_flags &= ~BLKID_BIC_FL_CHANGED;
+               ret = 1;
+       }
+
+       fclose(file);
+       if (opened != filename) {
+               if (ret < 0) {
+                       unlink(opened);
+                       DBG(DEBUG_SAVE,
+                           printf("unlinked temp cache %s\n", opened));
+               } else {
+                       char *backup;
+
+                       backup = xmalloc(strlen(filename) + 5);
+                       sprintf(backup, "%s.old", filename);
+                       unlink(backup);
+                       link(filename, backup);
+                       free(backup);
+                       rename(opened, filename);
+                       DBG(DEBUG_SAVE,
+                           printf("moved temp cache %s\n", opened));
+               }
+       }
+
+errout:
+       free(tmp);
+       return ret;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+       blkid_cache cache = NULL;
+       int ret;
+
+       blkid_debug_mask = DEBUG_ALL;
+       if (argc > 2) {
+               fprintf(stderr, "Usage: %s [filename]\n"
+                       "Test loading/saving a cache (filename)\n", argv[0]);
+               exit(1);
+       }
+
+       if ((ret = blkid_get_cache(&cache, bb_dev_null)) != 0) {
+               fprintf(stderr, "%s: error creating cache (%d)\n",
+                       argv[0], ret);
+               exit(1);
+       }
+       if ((ret = blkid_probe_all(cache)) < 0) {
+               fprintf(stderr, "error (%d) probing devices\n", ret);
+               exit(1);
+       }
+       cache->bic_filename = blkid_strdup(argv[1]);
+
+       if ((ret = blkid_flush_cache(cache)) < 0) {
+               fprintf(stderr, "error (%d) saving cache\n", ret);
+               exit(1);
+       }
+
+       blkid_put_cache(cache);
+
+       return ret;
+}
+#endif
diff --git a/e2fsprogs/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..78e7cbd
--- /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 /* __E2FSBB_H__ */
diff --git a/e2fsprogs/old_e2fsprogs/e2fsck.c b/e2fsprogs/old_e2fsprogs/e2fsck.c
new file mode 100644 (file)
index 0000000..4887a57
--- /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(0);
+       if ((retval = ext2fs_write_inode(fs, ino, &inode)) != 0)
+               goto err_out;
+
+       group = ext2fs_group_of_ino(fs, ino);
+       ext2fs_unmark_inode_bitmap(fs->inode_map, ino);
+       ext2fs_mark_ib_dirty(fs);
+       fs->group_desc[group].bg_free_inodes_count++;
+       fs->super->s_free_inodes_count++;
+       return;
+
+err_out:
+       pctx.errcode = retval;
+       fix_problem(ctx, PR_0_ERR_MOVE_JOURNAL, &pctx);
+       fs->super->s_state &= ~EXT2_VALID_FS;
+       ext2fs_mark_super_dirty(fs);
+}
+
+/*
+ * 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(0);
+                                       inode->i_links_count = 0;
+                                       ext2fs_icount_store(ctx->inode_link_info,
+                                                           ino, 0);
+                                       e2fsck_write_inode(ctx, ino, inode,
+                                                          "pass1");
+                               }
+
+                       }
+                       /*
+                        * If dtime is set, offer to clear it.  mke2fs
+                        * version 0.2b created filesystems with the
+                        * dtime field set for the root and lost+found
+                        * directories.  We won't worry about
+                        * /lost+found, since that can be regenerated
+                        * easily.  But we will fix the root directory
+                        * as a special case.
+                        */
+                       if (inode->i_dtime && inode->i_links_count) {
+                               if (fix_problem(ctx, PR_1_ROOT_DTIME, &pctx)) {
+                                       inode->i_dtime = 0;
+                                       e2fsck_write_inode(ctx, ino, inode,
+                                                          "pass1");
+                               }
+                       }
+               } else if (ino == EXT2_JOURNAL_INO) {
+                       ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
+                       if (fs->super->s_journal_inum == EXT2_JOURNAL_INO) {
+                               if (!LINUX_S_ISREG(inode->i_mode) &&
+                                   fix_problem(ctx, PR_1_JOURNAL_BAD_MODE,
+                                               &pctx)) {
+                                       inode->i_mode = LINUX_S_IFREG;
+                                       e2fsck_write_inode(ctx, ino, inode,
+                                                          "pass1");
+                               }
+                               check_blocks(ctx, &pctx, block_buf);
+                               continue;
+                       }
+                       if ((inode->i_links_count || inode->i_blocks ||
+                            inode->i_blocks || inode->i_block[0]) &&
+                           fix_problem(ctx, PR_1_JOURNAL_INODE_NOT_CLEAR,
+                                       &pctx)) {
+                               memset(inode, 0, inode_size);
+                               ext2fs_icount_store(ctx->inode_link_info,
+                                                   ino, 0);
+                               e2fsck_write_inode_full(ctx, ino, inode,
+                                                       inode_size, "pass1");
+                       }
+               } else if (ino < EXT2_FIRST_INODE(fs->super)) {
+                       int     problem = 0;
+
+                       ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
+                       if (ino == EXT2_BOOT_LOADER_INO) {
+                               if (LINUX_S_ISDIR(inode->i_mode))
+                                       problem = PR_1_RESERVED_BAD_MODE;
+                       } else if (ino == EXT2_RESIZE_INO) {
+                               if (inode->i_mode &&
+                                   !LINUX_S_ISREG(inode->i_mode))
+                                       problem = PR_1_RESERVED_BAD_MODE;
+                       } else {
+                               if (inode->i_mode != 0)
+                                       problem = PR_1_RESERVED_BAD_MODE;
+                       }
+                       if (problem) {
+                               if (fix_problem(ctx, problem, &pctx)) {
+                                       inode->i_mode = 0;
+                                       e2fsck_write_inode(ctx, ino, inode,
+                                                          "pass1");
+                               }
+                       }
+                       check_blocks(ctx, &pctx, block_buf);
+                       continue;
+               }
+               /*
+                * Check for inodes who might have been part of the
+                * orphaned list linked list.  They should have gotten
+                * dealt with by now, unless the list had somehow been
+                * corrupted.
+                *
+                * FIXME: In the future, inodes which are still in use
+                * (and which are therefore) pending truncation should
+                * be handled specially.  Right now we just clear the
+                * dtime field, and the normal e2fsck handling of
+                * inodes where i_size and the inode blocks are
+                * inconsistent is to fix i_size, instead of releasing
+                * the extra blocks.  This won't catch the inodes that
+                * was at the end of the orphan list, but it's better
+                * than nothing.  The right answer is that there
+                * shouldn't be any bugs in the orphan list handling.  :-)
+                */
+               if (inode->i_dtime && !busted_fs_time &&
+                   inode->i_dtime < ctx->fs->super->s_inodes_count) {
+                       if (fix_problem(ctx, PR_1_LOW_DTIME, &pctx)) {
+                               inode->i_dtime = inode->i_links_count ?
+                                       0 : time(0);
+                               e2fsck_write_inode(ctx, ino, inode,
+                                                  "pass1");
+                       }
+               }
+
+               /*
+                * This code assumes that deleted inodes have
+                * i_links_count set to 0.
+                */
+               if (!inode->i_links_count) {
+                       if (!inode->i_dtime && inode->i_mode) {
+                               if (fix_problem(ctx,
+                                           PR_1_ZERO_DTIME, &pctx)) {
+                                       inode->i_dtime = time(0);
+                                       e2fsck_write_inode(ctx, ino, inode,
+                                                          "pass1");
+                               }
+                       }
+                       continue;
+               }
+               /*
+                * n.b.  0.3c ext2fs code didn't clear i_links_count for
+                * deleted files.  Oops.
+                *
+                * Since all new ext2 implementations get this right,
+                * we now assume that the case of non-zero
+                * i_links_count and non-zero dtime means that we
+                * should keep the file, not delete it.
+                *
+                */
+               if (inode->i_dtime) {
+                       if (fix_problem(ctx, PR_1_SET_DTIME, &pctx)) {
+                               inode->i_dtime = 0;
+                               e2fsck_write_inode(ctx, ino, inode, "pass1");
+                       }
+               }
+
+               ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
+               switch (fs->super->s_creator_os) {
+                   case EXT2_OS_LINUX:
+                       frag = inode->osd2.linux2.l_i_frag;
+                       fsize = inode->osd2.linux2.l_i_fsize;
+                       break;
+                   case EXT2_OS_HURD:
+                       frag = inode->osd2.hurd2.h_i_frag;
+                       fsize = inode->osd2.hurd2.h_i_fsize;
+                       break;
+                   case EXT2_OS_MASIX:
+                       frag = inode->osd2.masix2.m_i_frag;
+                       fsize = inode->osd2.masix2.m_i_fsize;
+                       break;
+                   default:
+                       frag = fsize = 0;
+               }
+
+               if (inode->i_faddr || frag || fsize ||
+                   (LINUX_S_ISDIR(inode->i_mode) && inode->i_dir_acl))
+                       mark_inode_bad(ctx, ino);
+               if (inode->i_flags & EXT2_IMAGIC_FL) {
+                       if (imagic_fs) {
+                               if (!ctx->inode_imagic_map)
+                                       alloc_imagic_map(ctx);
+                               ext2fs_mark_inode_bitmap(ctx->inode_imagic_map,
+                                                        ino);
+                       } else {
+                               if (fix_problem(ctx, PR_1_SET_IMAGIC, &pctx)) {
+                                       inode->i_flags &= ~EXT2_IMAGIC_FL;
+                                       e2fsck_write_inode(ctx, ino,
+                                                          inode, "pass1");
+                               }
+                       }
+               }
+
+               check_inode_extra_space(ctx, &pctx);
+
+               if (LINUX_S_ISDIR(inode->i_mode)) {
+                       ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino);
+                       e2fsck_add_dir_info(ctx, ino, 0);
+                       ctx->fs_directory_count++;
+               } else if (LINUX_S_ISREG (inode->i_mode)) {
+                       ext2fs_mark_inode_bitmap(ctx->inode_reg_map, ino);
+                       ctx->fs_regular_count++;
+               } else if (LINUX_S_ISCHR (inode->i_mode) &&
+                          e2fsck_pass1_check_device_inode(fs, inode)) {
+                       check_immutable(ctx, &pctx);
+                       check_size(ctx, &pctx);
+                       ctx->fs_chardev_count++;
+               } else if (LINUX_S_ISBLK (inode->i_mode) &&
+                          e2fsck_pass1_check_device_inode(fs, inode)) {
+                       check_immutable(ctx, &pctx);
+                       check_size(ctx, &pctx);
+                       ctx->fs_blockdev_count++;
+               } else if (LINUX_S_ISLNK (inode->i_mode) &&
+                          e2fsck_pass1_check_symlink(fs, inode, block_buf)) {
+                       check_immutable(ctx, &pctx);
+                       ctx->fs_symlinks_count++;
+                       if (ext2fs_inode_data_blocks(fs, inode) == 0) {
+                               ctx->fs_fast_symlinks_count++;
+                               check_blocks(ctx, &pctx, block_buf);
+                               continue;
+                       }
+               }
+               else if (LINUX_S_ISFIFO (inode->i_mode) &&
+                        e2fsck_pass1_check_device_inode(fs, inode)) {
+                       check_immutable(ctx, &pctx);
+                       check_size(ctx, &pctx);
+                       ctx->fs_fifo_count++;
+               } else if ((LINUX_S_ISSOCK (inode->i_mode)) &&
+                          e2fsck_pass1_check_device_inode(fs, inode)) {
+                       check_immutable(ctx, &pctx);
+                       check_size(ctx, &pctx);
+                       ctx->fs_sockets_count++;
+               } else
+                       mark_inode_bad(ctx, ino);
+               if (inode->i_block[EXT2_IND_BLOCK])
+                       ctx->fs_ind_count++;
+               if (inode->i_block[EXT2_DIND_BLOCK])
+                       ctx->fs_dind_count++;
+               if (inode->i_block[EXT2_TIND_BLOCK])
+                       ctx->fs_tind_count++;
+               if (inode->i_block[EXT2_IND_BLOCK] ||
+                   inode->i_block[EXT2_DIND_BLOCK] ||
+                   inode->i_block[EXT2_TIND_BLOCK] ||
+                   inode->i_file_acl) {
+                       inodes_to_process[process_inode_count].ino = ino;
+                       inodes_to_process[process_inode_count].inode = *inode;
+                       process_inode_count++;
+               } else
+                       check_blocks(ctx, &pctx, block_buf);
+
+               if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                       return;
+
+               if (process_inode_count >= ctx->process_inode_size) {
+                       process_inodes(ctx, block_buf);
+
+                       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                               return;
+               }
+       }
+       process_inodes(ctx, block_buf);
+       ext2fs_close_inode_scan(scan);
+       ehandler_operation(0);
+
+       /*
+        * If any extended attribute blocks' reference counts need to
+        * be adjusted, either up (ctx->refcount_extra), or down
+        * (ctx->refcount), then fix them.
+        */
+       if (ctx->refcount) {
+               adjust_extattr_refcount(ctx, ctx->refcount, block_buf, -1);
+               ea_refcount_free(ctx->refcount);
+               ctx->refcount = 0;
+       }
+       if (ctx->refcount_extra) {
+               adjust_extattr_refcount(ctx, ctx->refcount_extra,
+                                       block_buf, +1);
+               ea_refcount_free(ctx->refcount_extra);
+               ctx->refcount_extra = 0;
+       }
+
+       if (ctx->invalid_bitmaps)
+               handle_fs_bad_blocks(ctx);
+
+       /* We don't need the block_ea_map any more */
+       ext2fs_free_block_bitmap(ctx->block_ea_map);
+       ctx->block_ea_map = 0;
+
+       if (ctx->flags & E2F_FLAG_RESIZE_INODE) {
+               ext2fs_block_bitmap save_bmap;
+
+               save_bmap = fs->block_map;
+               fs->block_map = ctx->block_found_map;
+               clear_problem_context(&pctx);
+               pctx.errcode = ext2fs_create_resize_inode(fs);
+               if (pctx.errcode) {
+                       fix_problem(ctx, PR_1_RESIZE_INODE_CREATE, &pctx);
+                       /* Should never get here */
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+               e2fsck_read_inode(ctx, EXT2_RESIZE_INO, inode,
+                                 "recreate inode");
+               inode->i_mtime = time(0);
+               e2fsck_write_inode(ctx, EXT2_RESIZE_INO, inode,
+                                 "recreate inode");
+               fs->block_map = save_bmap;
+               ctx->flags &= ~E2F_FLAG_RESIZE_INODE;
+       }
+
+       if (ctx->flags & E2F_FLAG_RESTART) {
+               /*
+                * Only the master copy of the superblock and block
+                * group descriptors are going to be written during a
+                * restart, so set the superblock to be used to be the
+                * master superblock.
+                */
+               ctx->use_superblock = 0;
+               unwind_pass1();
+               goto endit;
+       }
+
+       if (ctx->block_dup_map) {
+               if (ctx->options & E2F_OPT_PREEN) {
+                       clear_problem_context(&pctx);
+                       fix_problem(ctx, PR_1_DUP_BLOCKS_PREENSTOP, &pctx);
+               }
+               e2fsck_pass1_dupblocks(ctx, block_buf);
+       }
+       ext2fs_free_mem(&inodes_to_process);
+endit:
+       e2fsck_use_inode_shortcuts(ctx, 0);
+
+       ext2fs_free_mem(&block_buf);
+       ext2fs_free_mem(&inode);
+
+}
+
+/*
+ * When the inode_scan routines call this callback at the end of the
+ * glock group, call process_inodes.
+ */
+static errcode_t scan_callback(ext2_filsys fs,
+                              dgrp_t group, void * priv_data)
+{
+       struct scan_callback_struct *scan_struct;
+       e2fsck_t ctx;
+
+       scan_struct = (struct scan_callback_struct *) priv_data;
+       ctx = scan_struct->ctx;
+
+       process_inodes((e2fsck_t) fs->priv_data, scan_struct->block_buf);
+
+       if (ctx->progress)
+               if ((ctx->progress)(ctx, 1, group+1,
+                                   ctx->fs->group_desc_count))
+                       return EXT2_ET_CANCEL_REQUESTED;
+
+       return 0;
+}
+
+/*
+ * Process the inodes in the "inodes to process" list.
+ */
+static void process_inodes(e2fsck_t ctx, char *block_buf)
+{
+       int                     i;
+       struct ext2_inode       *old_stashed_inode;
+       ext2_ino_t              old_stashed_ino;
+       const char              *old_operation;
+       char                    buf[80];
+       struct problem_context  pctx;
+
+       /* begin process_inodes */
+       if (process_inode_count == 0)
+               return;
+       old_operation = ehandler_operation(0);
+       old_stashed_inode = ctx->stashed_inode;
+       old_stashed_ino = ctx->stashed_ino;
+       qsort(inodes_to_process, process_inode_count,
+                     sizeof(struct process_inode_block), process_inode_cmp);
+       clear_problem_context(&pctx);
+       for (i=0; i < process_inode_count; i++) {
+               pctx.inode = ctx->stashed_inode = &inodes_to_process[i].inode;
+               pctx.ino = ctx->stashed_ino = inodes_to_process[i].ino;
+               sprintf(buf, _("reading indirect blocks of inode %u"),
+                       pctx.ino);
+               ehandler_operation(buf);
+               check_blocks(ctx, &pctx, block_buf);
+               if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                       break;
+       }
+       ctx->stashed_inode = old_stashed_inode;
+       ctx->stashed_ino = old_stashed_ino;
+       process_inode_count = 0;
+       /* end process inodes */
+
+       ehandler_operation(old_operation);
+}
+
+static int process_inode_cmp(const void *a, const void *b)
+{
+       const struct process_inode_block *ib_a =
+               (const struct process_inode_block *) a;
+       const struct process_inode_block *ib_b =
+               (const struct process_inode_block *) b;
+       int     ret;
+
+       ret = (ib_a->inode.i_block[EXT2_IND_BLOCK] -
+              ib_b->inode.i_block[EXT2_IND_BLOCK]);
+       if (ret == 0)
+               ret = ib_a->inode.i_file_acl - ib_b->inode.i_file_acl;
+       return ret;
+}
+
+/*
+ * Mark an inode as being bad in some what
+ */
+static void mark_inode_bad(e2fsck_t ctx, ino_t ino)
+{
+       struct          problem_context pctx;
+
+       if (!ctx->inode_bad_map) {
+               clear_problem_context(&pctx);
+
+               pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs,
+                           _("bad inode map"), &ctx->inode_bad_map);
+               if (pctx.errcode) {
+                       pctx.num = 3;
+                       fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx);
+                       /* Should never get here */
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+       }
+       ext2fs_mark_inode_bitmap(ctx->inode_bad_map, ino);
+}
+
+
+/*
+ * This procedure will allocate the inode imagic table
+ */
+static void alloc_imagic_map(e2fsck_t ctx)
+{
+       struct          problem_context pctx;
+
+       clear_problem_context(&pctx);
+       pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs,
+                                             _("imagic inode map"),
+                                             &ctx->inode_imagic_map);
+       if (pctx.errcode) {
+               pctx.num = 5;
+               fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx);
+               /* Should never get here */
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+}
+
+/*
+ * Marks a block as in use, setting the dup_map if it's been set
+ * already.  Called by process_block and process_bad_block.
+ *
+ * WARNING: Assumes checks have already been done to make sure block
+ * is valid.  This is true in both process_block and process_bad_block.
+ */
+static void mark_block_used(e2fsck_t ctx, blk_t block)
+{
+       struct          problem_context pctx;
+
+       clear_problem_context(&pctx);
+
+       if (ext2fs_fast_test_block_bitmap(ctx->block_found_map, block)) {
+               if (!ctx->block_dup_map) {
+                       pctx.errcode = ext2fs_allocate_block_bitmap(ctx->fs,
+                             _("multiply claimed block map"),
+                             &ctx->block_dup_map);
+                       if (pctx.errcode) {
+                               pctx.num = 3;
+                               fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR,
+                                           &pctx);
+                               /* Should never get here */
+                               ctx->flags |= E2F_FLAG_ABORT;
+                               return;
+                       }
+               }
+               ext2fs_fast_mark_block_bitmap(ctx->block_dup_map, block);
+       } else {
+               ext2fs_fast_mark_block_bitmap(ctx->block_found_map, block);
+       }
+}
+
+/*
+ * Adjust the extended attribute block's reference counts at the end
+ * of pass 1, either by subtracting out references for EA blocks that
+ * are still referenced in ctx->refcount, or by adding references for
+ * EA blocks that had extra references as accounted for in
+ * ctx->refcount_extra.
+ */
+static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount,
+                                   char *block_buf, int adjust_sign)
+{
+       struct ext2_ext_attr_header     *header;
+       struct problem_context          pctx;
+       ext2_filsys                     fs = ctx->fs;
+       blk_t                           blk;
+       __u32                           should_be;
+       int                             count;
+
+       clear_problem_context(&pctx);
+
+       ea_refcount_intr_begin(refcount);
+       while (1) {
+               if ((blk = ea_refcount_intr_next(refcount, &count)) == 0)
+                       break;
+               pctx.blk = blk;
+               pctx.errcode = ext2fs_read_ext_attr(fs, blk, block_buf);
+               if (pctx.errcode) {
+                       fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx);
+                       return;
+               }
+               header = (struct ext2_ext_attr_header *) block_buf;
+               pctx.blkcount = header->h_refcount;
+               should_be = header->h_refcount + adjust_sign * count;
+               pctx.num = should_be;
+               if (fix_problem(ctx, PR_1_EXTATTR_REFCOUNT, &pctx)) {
+                       header->h_refcount = should_be;
+                       pctx.errcode = ext2fs_write_ext_attr(fs, blk,
+                                                            block_buf);
+                       if (pctx.errcode) {
+                               fix_problem(ctx, PR_1_EXTATTR_WRITE, &pctx);
+                               continue;
+                       }
+               }
+       }
+}
+
+/*
+ * Handle processing the extended attribute blocks
+ */
+static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx,
+                          char *block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       ext2_ino_t      ino = pctx->ino;
+       struct ext2_inode *inode = pctx->inode;
+       blk_t           blk;
+       char *          end;
+       struct ext2_ext_attr_header *header;
+       struct ext2_ext_attr_entry *entry;
+       int             count;
+       region_t        region;
+
+       blk = inode->i_file_acl;
+       if (blk == 0)
+               return 0;
+
+       /*
+        * If the Extended attribute flag isn't set, then a non-zero
+        * file acl means that the inode is corrupted.
+        *
+        * Or if the extended attribute block is an invalid block,
+        * then the inode is also corrupted.
+        */
+       if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR) ||
+           (blk < fs->super->s_first_data_block) ||
+           (blk >= fs->super->s_blocks_count)) {
+               mark_inode_bad(ctx, ino);
+               return 0;
+       }
+
+       /* If ea bitmap hasn't been allocated, create it */
+       if (!ctx->block_ea_map) {
+               pctx->errcode = ext2fs_allocate_block_bitmap(fs,
+                                                     _("ext attr block map"),
+                                                     &ctx->block_ea_map);
+               if (pctx->errcode) {
+                       pctx->num = 2;
+                       fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return 0;
+               }
+       }
+
+       /* Create the EA refcount structure if necessary */
+       if (!ctx->refcount) {
+               pctx->errcode = ea_refcount_create(0, &ctx->refcount);
+               if (pctx->errcode) {
+                       pctx->num = 1;
+                       fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return 0;
+               }
+       }
+
+       /* Have we seen this EA block before? */
+       if (ext2fs_fast_test_block_bitmap(ctx->block_ea_map, blk)) {
+               if (ea_refcount_decrement(ctx->refcount, blk, 0) == 0)
+                       return 1;
+               /* Ooops, this EA was referenced more than it stated */
+               if (!ctx->refcount_extra) {
+                       pctx->errcode = ea_refcount_create(0,
+                                          &ctx->refcount_extra);
+                       if (pctx->errcode) {
+                               pctx->num = 2;
+                               fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx);
+                               ctx->flags |= E2F_FLAG_ABORT;
+                               return 0;
+                       }
+               }
+               ea_refcount_increment(ctx->refcount_extra, blk, 0);
+               return 1;
+       }
+
+       /*
+        * OK, we haven't seen this EA block yet.  So we need to
+        * validate it
+        */
+       pctx->blk = blk;
+       pctx->errcode = ext2fs_read_ext_attr(fs, blk, block_buf);
+       if (pctx->errcode && fix_problem(ctx, PR_1_READ_EA_BLOCK, pctx))
+               goto clear_extattr;
+       header = (struct ext2_ext_attr_header *) block_buf;
+       pctx->blk = inode->i_file_acl;
+       if (((ctx->ext_attr_ver == 1) &&
+            (header->h_magic != EXT2_EXT_ATTR_MAGIC_v1)) ||
+           ((ctx->ext_attr_ver == 2) &&
+            (header->h_magic != EXT2_EXT_ATTR_MAGIC))) {
+               if (fix_problem(ctx, PR_1_BAD_EA_BLOCK, pctx))
+                       goto clear_extattr;
+       }
+
+       if (header->h_blocks != 1) {
+               if (fix_problem(ctx, PR_1_EA_MULTI_BLOCK, pctx))
+                       goto clear_extattr;
+       }
+
+       region = region_create(0, fs->blocksize);
+       if (!region) {
+               fix_problem(ctx, PR_1_EA_ALLOC_REGION, pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return 0;
+       }
+       if (region_allocate(region, 0, sizeof(struct ext2_ext_attr_header))) {
+               if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx))
+                       goto clear_extattr;
+       }
+
+       entry = (struct ext2_ext_attr_entry *)(header+1);
+       end = block_buf + fs->blocksize;
+       while ((char *)entry < end && *(__u32 *)entry) {
+               if (region_allocate(region, (char *)entry - (char *)header,
+                                  EXT2_EXT_ATTR_LEN(entry->e_name_len))) {
+                       if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx))
+                               goto clear_extattr;
+               }
+               if ((ctx->ext_attr_ver == 1 &&
+                    (entry->e_name_len == 0 || entry->e_name_index != 0)) ||
+                   (ctx->ext_attr_ver == 2 &&
+                    entry->e_name_index == 0)) {
+                       if (fix_problem(ctx, PR_1_EA_BAD_NAME, pctx))
+                               goto clear_extattr;
+               }
+               if (entry->e_value_block != 0) {
+                       if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx))
+                               goto clear_extattr;
+               }
+               if (entry->e_value_size &&
+                   region_allocate(region, entry->e_value_offs,
+                                   EXT2_EXT_ATTR_SIZE(entry->e_value_size))) {
+                       if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx))
+                               goto clear_extattr;
+               }
+               entry = EXT2_EXT_ATTR_NEXT(entry);
+       }
+       if (region_allocate(region, (char *)entry - (char *)header, 4)) {
+               if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx))
+                       goto clear_extattr;
+       }
+       region_free(region);
+
+       count = header->h_refcount - 1;
+       if (count)
+               ea_refcount_store(ctx->refcount, blk, count);
+       mark_block_used(ctx, blk);
+       ext2fs_fast_mark_block_bitmap(ctx->block_ea_map, blk);
+
+       return 1;
+
+clear_extattr:
+       inode->i_file_acl = 0;
+       e2fsck_write_inode(ctx, ino, inode, "check_ext_attr");
+       return 0;
+}
+
+/* Returns 1 if bad htree, 0 if OK */
+static int handle_htree(e2fsck_t ctx, struct problem_context *pctx,
+                       ext2_ino_t ino FSCK_ATTR((unused)),
+                       struct ext2_inode *inode,
+                       char *block_buf)
+{
+       struct ext2_dx_root_info        *root;
+       ext2_filsys                     fs = ctx->fs;
+       errcode_t                       retval;
+       blk_t                           blk;
+
+       if ((!LINUX_S_ISDIR(inode->i_mode) &&
+            fix_problem(ctx, PR_1_HTREE_NODIR, pctx)) ||
+           (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) &&
+            fix_problem(ctx, PR_1_HTREE_SET, pctx)))
+               return 1;
+
+       blk = inode->i_block[0];
+       if (((blk == 0) ||
+            (blk < fs->super->s_first_data_block) ||
+            (blk >= fs->super->s_blocks_count)) &&
+           fix_problem(ctx, PR_1_HTREE_BADROOT, pctx))
+               return 1;
+
+       retval = io_channel_read_blk(fs->io, blk, 1, block_buf);
+       if (retval && fix_problem(ctx, PR_1_HTREE_BADROOT, pctx))
+               return 1;
+
+       /* XXX should check that beginning matches a directory */
+       root = (struct ext2_dx_root_info *) (block_buf + 24);
+
+       if ((root->reserved_zero || root->info_length < 8) &&
+           fix_problem(ctx, PR_1_HTREE_BADROOT, pctx))
+               return 1;
+
+       pctx->num = root->hash_version;
+       if ((root->hash_version != EXT2_HASH_LEGACY) &&
+           (root->hash_version != EXT2_HASH_HALF_MD4) &&
+           (root->hash_version != EXT2_HASH_TEA) &&
+           fix_problem(ctx, PR_1_HTREE_HASHV, pctx))
+               return 1;
+
+       if ((root->unused_flags & EXT2_HASH_FLAG_INCOMPAT) &&
+           fix_problem(ctx, PR_1_HTREE_INCOMPAT, pctx))
+               return 1;
+
+       pctx->num = root->indirect_levels;
+       if ((root->indirect_levels > 1) &&
+           fix_problem(ctx, PR_1_HTREE_DEPTH, pctx))
+               return 1;
+
+       return 0;
+}
+
+/*
+ * This subroutine is called on each inode to account for all of the
+ * blocks used by that inode.
+ */
+static void check_blocks(e2fsck_t ctx, struct problem_context *pctx,
+                        char *block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       struct process_block_struct_1 pb;
+       ext2_ino_t      ino = pctx->ino;
+       struct ext2_inode *inode = pctx->inode;
+       int             bad_size = 0;
+       int             dirty_inode = 0;
+       __u64           size;
+
+       pb.ino = ino;
+       pb.num_blocks = 0;
+       pb.last_block = -1;
+       pb.num_illegal_blocks = 0;
+       pb.suppress = 0; pb.clear = 0;
+       pb.fragmented = 0;
+       pb.compressed = 0;
+       pb.previous_block = 0;
+       pb.is_dir = LINUX_S_ISDIR(inode->i_mode);
+       pb.is_reg = LINUX_S_ISREG(inode->i_mode);
+       pb.max_blocks = 1 << (31 - fs->super->s_log_block_size);
+       pb.inode = inode;
+       pb.pctx = pctx;
+       pb.ctx = ctx;
+       pctx->ino = ino;
+       pctx->errcode = 0;
+
+       if (inode->i_flags & EXT2_COMPRBLK_FL) {
+               if (fs->super->s_feature_incompat &
+                   EXT2_FEATURE_INCOMPAT_COMPRESSION)
+                       pb.compressed = 1;
+               else {
+                       if (fix_problem(ctx, PR_1_COMPR_SET, pctx)) {
+                               inode->i_flags &= ~EXT2_COMPRBLK_FL;
+                               dirty_inode++;
+                       }
+               }
+       }
+
+       if (inode->i_file_acl && check_ext_attr(ctx, pctx, block_buf))
+               pb.num_blocks++;
+
+       if (ext2fs_inode_has_valid_blocks(inode))
+               pctx->errcode = ext2fs_block_iterate2(fs, ino,
+                                      pb.is_dir ? BLOCK_FLAG_HOLE : 0,
+                                      block_buf, process_block, &pb);
+       end_problem_latch(ctx, PR_LATCH_BLOCK);
+       end_problem_latch(ctx, PR_LATCH_TOOBIG);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               goto out;
+       if (pctx->errcode)
+               fix_problem(ctx, PR_1_BLOCK_ITERATE, pctx);
+
+       if (pb.fragmented && pb.num_blocks < fs->super->s_blocks_per_group)
+               ctx->fs_fragmented++;
+
+       if (pb.clear) {
+               inode->i_links_count = 0;
+               ext2fs_icount_store(ctx->inode_link_info, ino, 0);
+               inode->i_dtime = time(0);
+               dirty_inode++;
+               ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino);
+               ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino);
+               ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino);
+               /*
+                * The inode was probably partially accounted for
+                * before processing was aborted, so we need to
+                * restart the pass 1 scan.
+                */
+               ctx->flags |= E2F_FLAG_RESTART;
+               goto out;
+       }
+
+       if (inode->i_flags & EXT2_INDEX_FL) {
+               if (handle_htree(ctx, pctx, ino, inode, block_buf)) {
+                       inode->i_flags &= ~EXT2_INDEX_FL;
+                       dirty_inode++;
+               } else {
+#ifdef ENABLE_HTREE
+                       e2fsck_add_dx_dir(ctx, ino, pb.last_block+1);
+#endif
+               }
+       }
+       if (ctx->dirs_to_hash && pb.is_dir &&
+           !(inode->i_flags & EXT2_INDEX_FL) &&
+           ((inode->i_size / fs->blocksize) >= 3))
+               ext2fs_u32_list_add(ctx->dirs_to_hash, ino);
+
+       if (!pb.num_blocks && pb.is_dir) {
+               if (fix_problem(ctx, PR_1_ZERO_LENGTH_DIR, pctx)) {
+                       inode->i_links_count = 0;
+                       ext2fs_icount_store(ctx->inode_link_info, ino, 0);
+                       inode->i_dtime = time(0);
+                       dirty_inode++;
+                       ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino);
+                       ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino);
+                       ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino);
+                       ctx->fs_directory_count--;
+                       goto out;
+               }
+       }
+
+       pb.num_blocks *= (fs->blocksize / 512);
+
+       if (pb.is_dir) {
+               int nblock = inode->i_size >> EXT2_BLOCK_SIZE_BITS(fs->super);
+               if (nblock > (pb.last_block + 1))
+                       bad_size = 1;
+               else if (nblock < (pb.last_block + 1)) {
+                       if (((pb.last_block + 1) - nblock) >
+                           fs->super->s_prealloc_dir_blocks)
+                               bad_size = 2;
+               }
+       } else {
+               size = EXT2_I_SIZE(inode);
+               if ((pb.last_block >= 0) &&
+                   (size < (__u64) pb.last_block * fs->blocksize))
+                       bad_size = 3;
+               else if (size > ext2_max_sizes[fs->super->s_log_block_size])
+                       bad_size = 4;
+       }
+       /* i_size for symlinks is checked elsewhere */
+       if (bad_size && !LINUX_S_ISLNK(inode->i_mode)) {
+               pctx->num = (pb.last_block+1) * fs->blocksize;
+               if (fix_problem(ctx, PR_1_BAD_I_SIZE, pctx)) {
+                       inode->i_size = pctx->num;
+                       if (!LINUX_S_ISDIR(inode->i_mode))
+                               inode->i_size_high = pctx->num >> 32;
+                       dirty_inode++;
+               }
+               pctx->num = 0;
+       }
+       if (LINUX_S_ISREG(inode->i_mode) &&
+           (inode->i_size_high || inode->i_size & 0x80000000UL))
+               ctx->large_files++;
+       if (pb.num_blocks != inode->i_blocks) {
+               pctx->num = pb.num_blocks;
+               if (fix_problem(ctx, PR_1_BAD_I_BLOCKS, pctx)) {
+                       inode->i_blocks = pb.num_blocks;
+                       dirty_inode++;
+               }
+               pctx->num = 0;
+       }
+out:
+       if (dirty_inode)
+               e2fsck_write_inode(ctx, ino, inode, "check_blocks");
+}
+
+
+/*
+ * This is a helper function for check_blocks().
+ */
+static int process_block(ext2_filsys fs,
+                 blk_t *block_nr,
+                 e2_blkcnt_t blockcnt,
+                 blk_t ref_block FSCK_ATTR((unused)),
+                 int ref_offset FSCK_ATTR((unused)),
+                 void *priv_data)
+{
+       struct process_block_struct_1 *p;
+       struct problem_context *pctx;
+       blk_t   blk = *block_nr;
+       int     ret_code = 0;
+       int     problem = 0;
+       e2fsck_t        ctx;
+
+       p = (struct process_block_struct_1 *) priv_data;
+       pctx = p->pctx;
+       ctx = p->ctx;
+
+       if (p->compressed && (blk == EXT2FS_COMPRESSED_BLKADDR)) {
+               /* todo: Check that the comprblk_fl is high, that the
+                  blkaddr pattern looks right (all non-holes up to
+                  first EXT2FS_COMPRESSED_BLKADDR, then all
+                  EXT2FS_COMPRESSED_BLKADDR up to end of cluster),
+                  that the feature_incompat bit is high, and that the
+                  inode is a regular file.  If we're doing a "full
+                  check" (a concept introduced to e2fsck by e2compr,
+                  meaning that we look at data blocks as well as
+                  metadata) then call some library routine that
+                  checks the compressed data.  I'll have to think
+                  about this, because one particularly important
+                  problem to be able to fix is to recalculate the
+                  cluster size if necessary.  I think that perhaps
+                  we'd better do most/all e2compr-specific checks
+                  separately, after the non-e2compr checks.  If not
+                  doing a full check, it may be useful to test that
+                  the personality is linux; e.g. if it isn't then
+                  perhaps this really is just an illegal block. */
+               return 0;
+       }
+
+       if (blk == 0) {
+               if (p->is_dir == 0) {
+                       /*
+                        * Should never happen, since only directories
+                        * get called with BLOCK_FLAG_HOLE
+                        */
+#ifdef DEBUG_E2FSCK
+                       printf("process_block() called with blk == 0, "
+                              "blockcnt=%d, inode %lu???\n",
+                              blockcnt, p->ino);
+#endif
+                       return 0;
+               }
+               if (blockcnt < 0)
+                       return 0;
+               if (blockcnt * fs->blocksize < p->inode->i_size) {
+                       goto mark_dir;
+               }
+               return 0;
+       }
+
+       /*
+        * Simplistic fragmentation check.  We merely require that the
+        * file be contiguous.  (Which can never be true for really
+        * big files that are greater than a block group.)
+        */
+       if (!HOLE_BLKADDR(p->previous_block)) {
+               if (p->previous_block+1 != blk)
+                       p->fragmented = 1;
+       }
+       p->previous_block = blk;
+
+       if (p->is_dir && blockcnt > (1 << (21 - fs->super->s_log_block_size)))
+               problem = PR_1_TOOBIG_DIR;
+       if (p->is_reg && p->num_blocks+1 >= p->max_blocks)
+               problem = PR_1_TOOBIG_REG;
+       if (!p->is_dir && !p->is_reg && blockcnt > 0)
+               problem = PR_1_TOOBIG_SYMLINK;
+
+       if (blk < fs->super->s_first_data_block ||
+           blk >= fs->super->s_blocks_count)
+               problem = PR_1_ILLEGAL_BLOCK_NUM;
+
+       if (problem) {
+               p->num_illegal_blocks++;
+               if (!p->suppress && (p->num_illegal_blocks % 12) == 0) {
+                       if (fix_problem(ctx, PR_1_TOO_MANY_BAD_BLOCKS, pctx)) {
+                               p->clear = 1;
+                               return BLOCK_ABORT;
+                       }
+                       if (fix_problem(ctx, PR_1_SUPPRESS_MESSAGES, pctx)) {
+                               p->suppress = 1;
+                               set_latch_flags(PR_LATCH_BLOCK,
+                                               PRL_SUPPRESS, 0);
+                       }
+               }
+               pctx->blk = blk;
+               pctx->blkcount = blockcnt;
+               if (fix_problem(ctx, problem, pctx)) {
+                       blk = *block_nr = 0;
+                       ret_code = BLOCK_CHANGED;
+                       goto mark_dir;
+               } else
+                       return 0;
+       }
+
+       if (p->ino == EXT2_RESIZE_INO) {
+               /*
+                * The resize inode has already be sanity checked
+                * during pass #0 (the superblock checks).  All we
+                * have to do is mark the double indirect block as
+                * being in use; all of the other blocks are handled
+                * by mark_table_blocks()).
+                */
+               if (blockcnt == BLOCK_COUNT_DIND)
+                       mark_block_used(ctx, blk);
+       } else
+               mark_block_used(ctx, blk);
+       p->num_blocks++;
+       if (blockcnt >= 0)
+               p->last_block = blockcnt;
+mark_dir:
+       if (p->is_dir && (blockcnt >= 0)) {
+               pctx->errcode = ext2fs_add_dir_block(fs->dblist, p->ino,
+                                                   blk, blockcnt);
+               if (pctx->errcode) {
+                       pctx->blk = blk;
+                       pctx->num = blockcnt;
+                       fix_problem(ctx, PR_1_ADD_DBLOCK, pctx);
+                       /* Should never get here */
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return BLOCK_ABORT;
+               }
+       }
+       return ret_code;
+}
+
+static int process_bad_block(ext2_filsys fs FSCK_ATTR((unused)),
+                     blk_t *block_nr,
+                     e2_blkcnt_t blockcnt,
+                     blk_t ref_block FSCK_ATTR((unused)),
+                     int ref_offset FSCK_ATTR((unused)),
+                     void *priv_data EXT2FS_ATTR((unused)))
+{
+       /*
+        * Note: This function processes blocks for the bad blocks
+        * inode, which is never compressed.  So we don't use HOLE_BLKADDR().
+        */
+
+       printf("Unrecoverable Error: Found %"PRIi64" bad blocks starting at block number: %u\n", blockcnt, *block_nr);
+       return BLOCK_ERROR;
+}
+
+/*
+ * This routine gets called at the end of pass 1 if bad blocks are
+ * detected in the superblock, group descriptors, inode_bitmaps, or
+ * block bitmaps.  At this point, all of the blocks have been mapped
+ * out, so we can try to allocate new block(s) to replace the bad
+ * blocks.
+ */
+static void handle_fs_bad_blocks(e2fsck_t ctx)
+{
+       printf("Bad blocks detected on your filesystem\n"
+               "You should get your data off as the device will soon die\n");
+}
+
+/*
+ * This routine marks all blocks which are used by the superblock,
+ * group descriptors, inode bitmaps, and block bitmaps.
+ */
+static void mark_table_blocks(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       blk_t   block, b;
+       dgrp_t  i;
+       int     j;
+       struct problem_context pctx;
+
+       clear_problem_context(&pctx);
+
+       block = fs->super->s_first_data_block;
+       for (i = 0; i < fs->group_desc_count; i++) {
+               pctx.group = i;
+
+               ext2fs_reserve_super_and_bgd(fs, i, ctx->block_found_map);
+
+               /*
+                * Mark the blocks used for the inode table
+                */
+               if (fs->group_desc[i].bg_inode_table) {
+                       for (j = 0, b = fs->group_desc[i].bg_inode_table;
+                            j < fs->inode_blocks_per_group;
+                            j++, b++) {
+                               if (ext2fs_test_block_bitmap(ctx->block_found_map,
+                                                            b)) {
+                                       pctx.blk = b;
+                                       if (fix_problem(ctx,
+                                               PR_1_ITABLE_CONFLICT, &pctx)) {
+                                               ctx->invalid_inode_table_flag[i]++;
+                                               ctx->invalid_bitmaps++;
+                                       }
+                               } else {
+                                   ext2fs_mark_block_bitmap(ctx->block_found_map,
+                                                            b);
+                               }
+                       }
+               }
+
+               /*
+                * Mark block used for the block bitmap
+                */
+               if (fs->group_desc[i].bg_block_bitmap) {
+                       if (ext2fs_test_block_bitmap(ctx->block_found_map,
+                                    fs->group_desc[i].bg_block_bitmap)) {
+                               pctx.blk = fs->group_desc[i].bg_block_bitmap;
+                               if (fix_problem(ctx, PR_1_BB_CONFLICT, &pctx)) {
+                                       ctx->invalid_block_bitmap_flag[i]++;
+                                       ctx->invalid_bitmaps++;
+                               }
+                       } else {
+                           ext2fs_mark_block_bitmap(ctx->block_found_map,
+                                    fs->group_desc[i].bg_block_bitmap);
+                   }
+
+               }
+               /*
+                * Mark block used for the inode bitmap
+                */
+               if (fs->group_desc[i].bg_inode_bitmap) {
+                       if (ext2fs_test_block_bitmap(ctx->block_found_map,
+                                    fs->group_desc[i].bg_inode_bitmap)) {
+                               pctx.blk = fs->group_desc[i].bg_inode_bitmap;
+                               if (fix_problem(ctx, PR_1_IB_CONFLICT, &pctx)) {
+                                       ctx->invalid_inode_bitmap_flag[i]++;
+                                       ctx->invalid_bitmaps++;
+                               }
+                       } else {
+                           ext2fs_mark_block_bitmap(ctx->block_found_map,
+                                    fs->group_desc[i].bg_inode_bitmap);
+                       }
+               }
+               block += fs->super->s_blocks_per_group;
+       }
+}
+
+/*
+ * Thes subroutines short circuits ext2fs_get_blocks and
+ * ext2fs_check_directory; we use them since we already have the inode
+ * structure, so there's no point in letting the ext2fs library read
+ * the inode again.
+ */
+static errcode_t pass1_get_blocks(ext2_filsys fs, ext2_ino_t ino,
+                                 blk_t *blocks)
+{
+       e2fsck_t ctx = (e2fsck_t) fs->priv_data;
+       int     i;
+
+       if ((ino != ctx->stashed_ino) || !ctx->stashed_inode)
+               return EXT2_ET_CALLBACK_NOTHANDLED;
+
+       for (i=0; i < EXT2_N_BLOCKS; i++)
+               blocks[i] = ctx->stashed_inode->i_block[i];
+       return 0;
+}
+
+static errcode_t pass1_read_inode(ext2_filsys fs, ext2_ino_t ino,
+                                 struct ext2_inode *inode)
+{
+       e2fsck_t ctx = (e2fsck_t) fs->priv_data;
+
+       if ((ino != ctx->stashed_ino) || !ctx->stashed_inode)
+               return EXT2_ET_CALLBACK_NOTHANDLED;
+       *inode = *ctx->stashed_inode;
+       return 0;
+}
+
+static errcode_t pass1_write_inode(ext2_filsys fs, ext2_ino_t ino,
+                           struct ext2_inode *inode)
+{
+       e2fsck_t ctx = (e2fsck_t) fs->priv_data;
+
+       if ((ino == ctx->stashed_ino) && ctx->stashed_inode)
+               *ctx->stashed_inode = *inode;
+       return EXT2_ET_CALLBACK_NOTHANDLED;
+}
+
+static errcode_t pass1_check_directory(ext2_filsys fs, ext2_ino_t ino)
+{
+       e2fsck_t ctx = (e2fsck_t) fs->priv_data;
+
+       if ((ino != ctx->stashed_ino) || !ctx->stashed_inode)
+               return EXT2_ET_CALLBACK_NOTHANDLED;
+
+       if (!LINUX_S_ISDIR(ctx->stashed_inode->i_mode))
+               return EXT2_ET_NO_DIRECTORY;
+       return 0;
+}
+
+void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool)
+{
+       ext2_filsys fs = ctx->fs;
+
+       if (bool) {
+               fs->get_blocks = pass1_get_blocks;
+               fs->check_directory = pass1_check_directory;
+               fs->read_inode = pass1_read_inode;
+               fs->write_inode = pass1_write_inode;
+               ctx->stashed_ino = 0;
+       } else {
+               fs->get_blocks = 0;
+               fs->check_directory = 0;
+               fs->read_inode = 0;
+               fs->write_inode = 0;
+       }
+}
+
+/*
+ * pass1b.c --- Pass #1b of e2fsck
+ *
+ * This file contains pass1B, pass1C, and pass1D of e2fsck.  They are
+ * only invoked if pass 1 discovered blocks which are in use by more
+ * than one inode.
+ *
+ * Pass1B scans the data blocks of all the inodes again, generating a
+ * complete list of duplicate blocks and which inodes have claimed
+ * them.
+ *
+ * Pass1C does a tree-traversal of the filesystem, to determine the
+ * parent directories of these inodes.  This step is necessary so that
+ * e2fsck can print out the pathnames of affected inodes.
+ *
+ * Pass1D is a reconciliation pass.  For each inode with duplicate
+ * blocks, the user is prompted if s/he would like to clone the file
+ * (so that the file gets a fresh copy of the duplicated blocks) or
+ * simply to delete the file.
+ *
+ */
+
+
+/* Needed for architectures where sizeof(int) != sizeof(void *) */
+#define INT_TO_VOIDPTR(val)  ((void *)(intptr_t)(val))
+#define VOIDPTR_TO_INT(ptr)  ((int)(intptr_t)(ptr))
+
+/* Define an extension to the ext2 library's block count information */
+#define BLOCK_COUNT_EXTATTR     (-5)
+
+struct block_el {
+       blk_t   block;
+       struct block_el *next;
+};
+
+struct inode_el {
+       ext2_ino_t      inode;
+       struct inode_el *next;
+};
+
+struct dup_block {
+       int             num_bad;
+       struct inode_el *inode_list;
+};
+
+/*
+ * This structure stores information about a particular inode which
+ * is sharing blocks with other inodes.  This information is collected
+ * to display to the user, so that the user knows what files he or she
+ * is dealing with, when trying to decide how to resolve the conflict
+ * of multiply-claimed blocks.
+ */
+struct dup_inode {
+       ext2_ino_t              dir;
+       int                     num_dupblocks;
+       struct ext2_inode       inode;
+       struct block_el         *block_list;
+};
+
+static int process_pass1b_block(ext2_filsys fs, blk_t   *blocknr,
+                               e2_blkcnt_t blockcnt, blk_t ref_blk,
+                               int ref_offset, void *priv_data);
+static void delete_file(e2fsck_t ctx, ext2_ino_t ino,
+                       struct dup_inode *dp, char *block_buf);
+static int clone_file(e2fsck_t ctx, ext2_ino_t ino,
+                     struct dup_inode *dp, char* block_buf);
+static int check_if_fs_block(e2fsck_t ctx, blk_t test_blk);
+
+static void pass1b(e2fsck_t ctx, char *block_buf);
+static void pass1c(e2fsck_t ctx, char *block_buf);
+static void pass1d(e2fsck_t ctx, char *block_buf);
+
+static int dup_inode_count = 0;
+
+static dict_t blk_dict, ino_dict;
+
+static ext2fs_inode_bitmap inode_dup_map;
+
+static int dict_int_cmp(const void *a, const void *b)
+{
+       intptr_t        ia, ib;
+
+       ia = (intptr_t)a;
+       ib = (intptr_t)b;
+
+       return (ia-ib);
+}
+
+/*
+ * Add a duplicate block record
+ */
+static void add_dupe(e2fsck_t ctx, ext2_ino_t ino, blk_t blk,
+                    struct ext2_inode *inode)
+{
+       dnode_t *n;
+       struct dup_block        *db;
+       struct dup_inode        *di;
+       struct block_el         *blk_el;
+       struct inode_el         *ino_el;
+
+       n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(blk));
+       if (n)
+               db = (struct dup_block *) dnode_get(n);
+       else {
+               db = (struct dup_block *) e2fsck_allocate_memory(ctx,
+                        sizeof(struct dup_block), "duplicate block header");
+               db->num_bad = 0;
+               db->inode_list = 0;
+               dict_alloc_insert(&blk_dict, INT_TO_VOIDPTR(blk), db);
+       }
+       ino_el = (struct inode_el *) e2fsck_allocate_memory(ctx,
+                        sizeof(struct inode_el), "inode element");
+       ino_el->inode = ino;
+       ino_el->next = db->inode_list;
+       db->inode_list = ino_el;
+       db->num_bad++;
+
+       n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(ino));
+       if (n)
+               di = (struct dup_inode *) dnode_get(n);
+       else {
+               di = (struct dup_inode *) e2fsck_allocate_memory(ctx,
+                        sizeof(struct dup_inode), "duplicate inode header");
+               di->dir = (ino == EXT2_ROOT_INO) ? EXT2_ROOT_INO : 0;
+               di->num_dupblocks = 0;
+               di->block_list = 0;
+               di->inode = *inode;
+               dict_alloc_insert(&ino_dict, INT_TO_VOIDPTR(ino), di);
+       }
+       blk_el = (struct block_el *) e2fsck_allocate_memory(ctx,
+                        sizeof(struct block_el), "block element");
+       blk_el->block = blk;
+       blk_el->next = di->block_list;
+       di->block_list = blk_el;
+       di->num_dupblocks++;
+}
+
+/*
+ * Free a duplicate inode record
+ */
+static void inode_dnode_free(dnode_t *node)
+{
+       struct dup_inode        *di;
+       struct block_el         *p, *next;
+
+       di = (struct dup_inode *) dnode_get(node);
+       for (p = di->block_list; p; p = next) {
+               next = p->next;
+               free(p);
+       }
+       free(node);
+}
+
+/*
+ * Free a duplicate block record
+ */
+static void block_dnode_free(dnode_t *node)
+{
+       struct dup_block        *db;
+       struct inode_el         *p, *next;
+
+       db = (struct dup_block *) dnode_get(node);
+       for (p = db->inode_list; p; p = next) {
+               next = p->next;
+               free(p);
+       }
+       free(node);
+}
+
+
+/*
+ * Main procedure for handling duplicate blocks
+ */
+void e2fsck_pass1_dupblocks(e2fsck_t ctx, char *block_buf)
+{
+       ext2_filsys             fs = ctx->fs;
+       struct problem_context  pctx;
+
+       clear_problem_context(&pctx);
+
+       pctx.errcode = ext2fs_allocate_inode_bitmap(fs,
+                     _("multiply claimed inode map"), &inode_dup_map);
+       if (pctx.errcode) {
+               fix_problem(ctx, PR_1B_ALLOCATE_IBITMAP_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+       dict_init(&ino_dict, DICTCOUNT_T_MAX, dict_int_cmp);
+       dict_init(&blk_dict, DICTCOUNT_T_MAX, dict_int_cmp);
+       dict_set_allocator(&ino_dict, inode_dnode_free);
+       dict_set_allocator(&blk_dict, block_dnode_free);
+
+       pass1b(ctx, block_buf);
+       pass1c(ctx, block_buf);
+       pass1d(ctx, block_buf);
+
+       /*
+        * Time to free all of the accumulated data structures that we
+        * don't need anymore.
+        */
+       dict_free_nodes(&ino_dict);
+       dict_free_nodes(&blk_dict);
+}
+
+/*
+ * Scan the inodes looking for inodes that contain duplicate blocks.
+ */
+struct process_block_struct_1b {
+       e2fsck_t        ctx;
+       ext2_ino_t      ino;
+       int             dup_blocks;
+       struct ext2_inode *inode;
+       struct problem_context *pctx;
+};
+
+static void pass1b(e2fsck_t ctx, char *block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       ext2_ino_t ino;
+       struct ext2_inode inode;
+       ext2_inode_scan scan;
+       struct process_block_struct_1b pb;
+       struct problem_context pctx;
+
+       clear_problem_context(&pctx);
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               fix_problem(ctx, PR_1B_PASS_HEADER, &pctx);
+       pctx.errcode = ext2fs_open_inode_scan(fs, ctx->inode_buffer_blocks,
+                                             &scan);
+       if (pctx.errcode) {
+               fix_problem(ctx, PR_1B_ISCAN_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+       ctx->stashed_inode = &inode;
+       pb.ctx = ctx;
+       pb.pctx = &pctx;
+       pctx.str = "pass1b";
+       while (1) {
+               pctx.errcode = ext2fs_get_next_inode(scan, &ino, &inode);
+               if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE)
+                       continue;
+               if (pctx.errcode) {
+                       fix_problem(ctx, PR_1B_ISCAN_ERROR, &pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+               if (!ino)
+                       break;
+               pctx.ino = ctx->stashed_ino = ino;
+               if ((ino != EXT2_BAD_INO) &&
+                   !ext2fs_test_inode_bitmap(ctx->inode_used_map, ino))
+                       continue;
+
+               pb.ino = ino;
+               pb.dup_blocks = 0;
+               pb.inode = &inode;
+
+               if (ext2fs_inode_has_valid_blocks(&inode) ||
+                   (ino == EXT2_BAD_INO))
+                       pctx.errcode = ext2fs_block_iterate2(fs, ino,
+                                    0, block_buf, process_pass1b_block, &pb);
+               if (inode.i_file_acl)
+                       process_pass1b_block(fs, &inode.i_file_acl,
+                                            BLOCK_COUNT_EXTATTR, 0, 0, &pb);
+               if (pb.dup_blocks) {
+                       end_problem_latch(ctx, PR_LATCH_DBLOCK);
+                       if (ino >= EXT2_FIRST_INODE(fs->super) ||
+                           ino == EXT2_ROOT_INO)
+                               dup_inode_count++;
+               }
+               if (pctx.errcode)
+                       fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx);
+       }
+       ext2fs_close_inode_scan(scan);
+       e2fsck_use_inode_shortcuts(ctx, 0);
+}
+
+static int process_pass1b_block(ext2_filsys fs FSCK_ATTR((unused)),
+                               blk_t   *block_nr,
+                               e2_blkcnt_t blockcnt FSCK_ATTR((unused)),
+                               blk_t ref_blk FSCK_ATTR((unused)),
+                               int ref_offset FSCK_ATTR((unused)),
+                               void *priv_data)
+{
+       struct process_block_struct_1b *p;
+       e2fsck_t ctx;
+
+       if (HOLE_BLKADDR(*block_nr))
+               return 0;
+       p = (struct process_block_struct_1b *) priv_data;
+       ctx = p->ctx;
+
+       if (!ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr))
+               return 0;
+
+       /* OK, this is a duplicate block */
+       if (p->ino != EXT2_BAD_INO) {
+               p->pctx->blk = *block_nr;
+               fix_problem(ctx, PR_1B_DUP_BLOCK, p->pctx);
+       }
+       p->dup_blocks++;
+       ext2fs_mark_inode_bitmap(inode_dup_map, p->ino);
+
+       add_dupe(ctx, p->ino, *block_nr, p->inode);
+
+       return 0;
+}
+
+/*
+ * Pass 1c: Scan directories for inodes with duplicate blocks.  This
+ * is used so that we can print pathnames when prompting the user for
+ * what to do.
+ */
+struct search_dir_struct {
+       int             count;
+       ext2_ino_t      first_inode;
+       ext2_ino_t      max_inode;
+};
+
+static int search_dirent_proc(ext2_ino_t dir, int entry,
+                             struct ext2_dir_entry *dirent,
+                             int offset FSCK_ATTR((unused)),
+                             int blocksize FSCK_ATTR((unused)),
+                             char *buf FSCK_ATTR((unused)),
+                             void *priv_data)
+{
+       struct search_dir_struct *sd;
+       struct dup_inode        *p;
+       dnode_t                 *n;
+
+       sd = (struct search_dir_struct *) priv_data;
+
+       if (dirent->inode > sd->max_inode)
+               /* Should abort this inode, but not everything */
+               return 0;
+
+       if ((dirent->inode < sd->first_inode) || (entry < DIRENT_OTHER_FILE) ||
+           !ext2fs_test_inode_bitmap(inode_dup_map, dirent->inode))
+               return 0;
+
+       n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(dirent->inode));
+       if (!n)
+               return 0;
+       p = (struct dup_inode *) dnode_get(n);
+       p->dir = dir;
+       sd->count--;
+
+       return sd->count ? 0 : DIRENT_ABORT;
+}
+
+
+static void pass1c(e2fsck_t ctx, char *block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       struct search_dir_struct sd;
+       struct problem_context pctx;
+
+       clear_problem_context(&pctx);
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               fix_problem(ctx, PR_1C_PASS_HEADER, &pctx);
+
+       /*
+        * Search through all directories to translate inodes to names
+        * (by searching for the containing directory for that inode.)
+        */
+       sd.count = dup_inode_count;
+       sd.first_inode = EXT2_FIRST_INODE(fs->super);
+       sd.max_inode = fs->super->s_inodes_count;
+       ext2fs_dblist_dir_iterate(fs->dblist, 0, block_buf,
+                                 search_dirent_proc, &sd);
+}
+
+static void pass1d(e2fsck_t ctx, char *block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       struct dup_inode        *p, *t;
+       struct dup_block        *q;
+       ext2_ino_t              *shared, ino;
+       int     shared_len;
+       int     i;
+       int     file_ok;
+       int     meta_data = 0;
+       struct problem_context pctx;
+       dnode_t *n, *m;
+       struct block_el *s;
+       struct inode_el *r;
+
+       clear_problem_context(&pctx);
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               fix_problem(ctx, PR_1D_PASS_HEADER, &pctx);
+       e2fsck_read_bitmaps(ctx);
+
+       pctx.num = dup_inode_count; /* dict_count(&ino_dict); */
+       fix_problem(ctx, PR_1D_NUM_DUP_INODES, &pctx);
+       shared = (ext2_ino_t *) e2fsck_allocate_memory(ctx,
+                               sizeof(ext2_ino_t) * dict_count(&ino_dict),
+                               "Shared inode list");
+       for (n = dict_first(&ino_dict); n; n = dict_next(&ino_dict, n)) {
+               p = (struct dup_inode *) dnode_get(n);
+               shared_len = 0;
+               file_ok = 1;
+               ino = (ext2_ino_t)VOIDPTR_TO_INT(dnode_getkey(n));
+               if (ino == EXT2_BAD_INO || ino == EXT2_RESIZE_INO)
+                       continue;
+
+               /*
+                * Find all of the inodes which share blocks with this
+                * one.  First we find all of the duplicate blocks
+                * belonging to this inode, and then search each block
+                * get the list of inodes, and merge them together.
+                */
+               for (s = p->block_list; s; s = s->next) {
+                       m = dict_lookup(&blk_dict, INT_TO_VOIDPTR(s->block));
+                       if (!m)
+                               continue; /* Should never happen... */
+                       q = (struct dup_block *) dnode_get(m);
+                       if (q->num_bad > 1)
+                               file_ok = 0;
+                       if (check_if_fs_block(ctx, s->block)) {
+                               file_ok = 0;
+                               meta_data = 1;
+                       }
+
+                       /*
+                        * Add all inodes used by this block to the
+                        * shared[] --- which is a unique list, so
+                        * if an inode is already in shared[], don't
+                        * add it again.
+                        */
+                       for (r = q->inode_list; r; r = r->next) {
+                               if (r->inode == ino)
+                                       continue;
+                               for (i = 0; i < shared_len; i++)
+                                       if (shared[i] == r->inode)
+                                               break;
+                               if (i == shared_len) {
+                                       shared[shared_len++] = r->inode;
+                               }
+                       }
+               }
+
+               /*
+                * Report the inode that we are working on
+                */
+               pctx.inode = &p->inode;
+               pctx.ino = ino;
+               pctx.dir = p->dir;
+               pctx.blkcount = p->num_dupblocks;
+               pctx.num = meta_data ? shared_len+1 : shared_len;
+               fix_problem(ctx, PR_1D_DUP_FILE, &pctx);
+               pctx.blkcount = 0;
+               pctx.num = 0;
+
+               if (meta_data)
+                       fix_problem(ctx, PR_1D_SHARE_METADATA, &pctx);
+
+               for (i = 0; i < shared_len; i++) {
+                       m = dict_lookup(&ino_dict, INT_TO_VOIDPTR(shared[i]));
+                       if (!m)
+                               continue; /* should never happen */
+                       t = (struct dup_inode *) dnode_get(m);
+                       /*
+                        * Report the inode that we are sharing with
+                        */
+                       pctx.inode = &t->inode;
+                       pctx.ino = shared[i];
+                       pctx.dir = t->dir;
+                       fix_problem(ctx, PR_1D_DUP_FILE_LIST, &pctx);
+               }
+               if (file_ok) {
+                       fix_problem(ctx, PR_1D_DUP_BLOCKS_DEALT, &pctx);
+                       continue;
+               }
+               if (fix_problem(ctx, PR_1D_CLONE_QUESTION, &pctx)) {
+                       pctx.errcode = clone_file(ctx, ino, p, block_buf);
+                       if (pctx.errcode)
+                               fix_problem(ctx, PR_1D_CLONE_ERROR, &pctx);
+                       else
+                               continue;
+               }
+               if (fix_problem(ctx, PR_1D_DELETE_QUESTION, &pctx))
+                       delete_file(ctx, ino, p, block_buf);
+               else
+                       ext2fs_unmark_valid(fs);
+       }
+       ext2fs_free_mem(&shared);
+}
+
+/*
+ * Drop the refcount on the dup_block structure, and clear the entry
+ * in the block_dup_map if appropriate.
+ */
+static void decrement_badcount(e2fsck_t ctx, blk_t block, struct dup_block *p)
+{
+       p->num_bad--;
+       if (p->num_bad <= 0 ||
+           (p->num_bad == 1 && !check_if_fs_block(ctx, block)))
+               ext2fs_unmark_block_bitmap(ctx->block_dup_map, block);
+}
+
+static int delete_file_block(ext2_filsys fs,
+                            blk_t      *block_nr,
+                            e2_blkcnt_t blockcnt FSCK_ATTR((unused)),
+                            blk_t ref_block FSCK_ATTR((unused)),
+                            int ref_offset FSCK_ATTR((unused)),
+                            void *priv_data)
+{
+       struct process_block_struct_1b *pb;
+       struct dup_block *p;
+       dnode_t *n;
+       e2fsck_t ctx;
+
+       pb = (struct process_block_struct_1b *) priv_data;
+       ctx = pb->ctx;
+
+       if (HOLE_BLKADDR(*block_nr))
+               return 0;
+
+       if (ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) {
+               n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(*block_nr));
+               if (n) {
+                       p = (struct dup_block *) dnode_get(n);
+                       decrement_badcount(ctx, *block_nr, p);
+               } else
+                       bb_error_msg(_("internal error; can't find dup_blk for %d"),
+                               *block_nr);
+       } else {
+               ext2fs_unmark_block_bitmap(ctx->block_found_map, *block_nr);
+               ext2fs_block_alloc_stats(fs, *block_nr, -1);
+       }
+
+       return 0;
+}
+
+static void delete_file(e2fsck_t ctx, ext2_ino_t ino,
+                       struct dup_inode *dp, char* block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       struct process_block_struct_1b pb;
+       struct ext2_inode       inode;
+       struct problem_context  pctx;
+       unsigned int            count;
+
+       clear_problem_context(&pctx);
+       pctx.ino = pb.ino = ino;
+       pb.dup_blocks = dp->num_dupblocks;
+       pb.ctx = ctx;
+       pctx.str = "delete_file";
+
+       e2fsck_read_inode(ctx, ino, &inode, "delete_file");
+       if (ext2fs_inode_has_valid_blocks(&inode))
+               pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf,
+                                                    delete_file_block, &pb);
+       if (pctx.errcode)
+               fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx);
+       ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino);
+       ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino);
+       if (ctx->inode_bad_map)
+               ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino);
+       ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode));
+
+       /* Inode may have changed by block_iterate, so reread it */
+       e2fsck_read_inode(ctx, ino, &inode, "delete_file");
+       inode.i_links_count = 0;
+       inode.i_dtime = time(0);
+       if (inode.i_file_acl &&
+           (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+               count = 1;
+               pctx.errcode = ext2fs_adjust_ea_refcount(fs, inode.i_file_acl,
+                                                  block_buf, -1, &count);
+               if (pctx.errcode == EXT2_ET_BAD_EA_BLOCK_NUM) {
+                       pctx.errcode = 0;
+                       count = 1;
+               }
+               if (pctx.errcode) {
+                       pctx.blk = inode.i_file_acl;
+                       fix_problem(ctx, PR_1B_ADJ_EA_REFCOUNT, &pctx);
+               }
+               /*
+                * If the count is zero, then arrange to have the
+                * block deleted.  If the block is in the block_dup_map,
+                * also call delete_file_block since it will take care
+                * of keeping the accounting straight.
+                */
+               if ((count == 0) ||
+                   ext2fs_test_block_bitmap(ctx->block_dup_map,
+                                            inode.i_file_acl))
+                       delete_file_block(fs, &inode.i_file_acl,
+                                         BLOCK_COUNT_EXTATTR, 0, 0, &pb);
+       }
+       e2fsck_write_inode(ctx, ino, &inode, "delete_file");
+}
+
+struct clone_struct {
+       errcode_t       errcode;
+       ext2_ino_t      dir;
+       char    *buf;
+       e2fsck_t ctx;
+};
+
+static int clone_file_block(ext2_filsys fs,
+                           blk_t       *block_nr,
+                           e2_blkcnt_t blockcnt,
+                           blk_t ref_block FSCK_ATTR((unused)),
+                           int ref_offset FSCK_ATTR((unused)),
+                           void *priv_data)
+{
+       struct dup_block *p;
+       blk_t   new_block;
+       errcode_t       retval;
+       struct clone_struct *cs = (struct clone_struct *) priv_data;
+       dnode_t *n;
+       e2fsck_t ctx;
+
+       ctx = cs->ctx;
+
+       if (HOLE_BLKADDR(*block_nr))
+               return 0;
+
+       if (ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) {
+               n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(*block_nr));
+               if (n) {
+                       p = (struct dup_block *) dnode_get(n);
+                       retval = ext2fs_new_block(fs, 0, ctx->block_found_map,
+                                                 &new_block);
+                       if (retval) {
+                               cs->errcode = retval;
+                               return BLOCK_ABORT;
+                       }
+                       if (cs->dir && (blockcnt >= 0)) {
+                               retval = ext2fs_set_dir_block(fs->dblist,
+                                     cs->dir, new_block, blockcnt);
+                               if (retval) {
+                                       cs->errcode = retval;
+                                       return BLOCK_ABORT;
+                               }
+                       }
+
+                       retval = io_channel_read_blk(fs->io, *block_nr, 1,
+                                                    cs->buf);
+                       if (retval) {
+                               cs->errcode = retval;
+                               return BLOCK_ABORT;
+                       }
+                       retval = io_channel_write_blk(fs->io, new_block, 1,
+                                                     cs->buf);
+                       if (retval) {
+                               cs->errcode = retval;
+                               return BLOCK_ABORT;
+                       }
+                       decrement_badcount(ctx, *block_nr, p);
+                       *block_nr = new_block;
+                       ext2fs_mark_block_bitmap(ctx->block_found_map,
+                                                new_block);
+                       ext2fs_mark_block_bitmap(fs->block_map, new_block);
+                       return BLOCK_CHANGED;
+               } else
+                       bb_error_msg(_("internal error; can't find dup_blk for %d"),
+                               *block_nr);
+       }
+       return 0;
+}
+
+static int clone_file(e2fsck_t ctx, ext2_ino_t ino,
+                     struct dup_inode *dp, char* block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       errcode_t       retval;
+       struct clone_struct cs;
+       struct problem_context  pctx;
+       blk_t           blk;
+       dnode_t         *n;
+       struct inode_el *ino_el;
+       struct dup_block        *db;
+       struct dup_inode        *di;
+
+       clear_problem_context(&pctx);
+       cs.errcode = 0;
+       cs.dir = 0;
+       cs.ctx = ctx;
+       retval = ext2fs_get_mem(fs->blocksize, &cs.buf);
+       if (retval)
+               return retval;
+
+       if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, ino))
+               cs.dir = ino;
+
+       pctx.ino = ino;
+       pctx.str = "clone_file";
+       if (ext2fs_inode_has_valid_blocks(&dp->inode))
+               pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf,
+                                                    clone_file_block, &cs);
+       ext2fs_mark_bb_dirty(fs);
+       if (pctx.errcode) {
+               fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx);
+               retval = pctx.errcode;
+               goto errout;
+       }
+       if (cs.errcode) {
+               bb_error_msg(_("returned from clone_file_block"));
+               retval = cs.errcode;
+               goto errout;
+       }
+       /* The inode may have changed on disk, so we have to re-read it */
+       e2fsck_read_inode(ctx, ino, &dp->inode, "clone file EA");
+       blk = dp->inode.i_file_acl;
+       if (blk && (clone_file_block(fs, &dp->inode.i_file_acl,
+                                    BLOCK_COUNT_EXTATTR, 0, 0, &cs) ==
+                   BLOCK_CHANGED)) {
+               e2fsck_write_inode(ctx, ino, &dp->inode, "clone file EA");
+               /*
+                * If we cloned the EA block, find all other inodes
+                * which refered to that EA block, and modify
+                * them to point to the new EA block.
+                */
+               n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(blk));
+               db = (struct dup_block *) dnode_get(n);
+               for (ino_el = db->inode_list; ino_el; ino_el = ino_el->next) {
+                       if (ino_el->inode == ino)
+                               continue;
+                       n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(ino_el->inode));
+                       di = (struct dup_inode *) dnode_get(n);
+                       if (di->inode.i_file_acl == blk) {
+                               di->inode.i_file_acl = dp->inode.i_file_acl;
+                               e2fsck_write_inode(ctx, ino_el->inode,
+                                          &di->inode, "clone file EA");
+                               decrement_badcount(ctx, blk, db);
+                       }
+               }
+       }
+       retval = 0;
+errout:
+       ext2fs_free_mem(&cs.buf);
+       return retval;
+}
+
+/*
+ * This routine returns 1 if a block overlaps with one of the superblocks,
+ * group descriptors, inode bitmaps, or block bitmaps.
+ */
+static int check_if_fs_block(e2fsck_t ctx, blk_t test_block)
+{
+       ext2_filsys fs = ctx->fs;
+       blk_t   block;
+       dgrp_t  i;
+
+       block = fs->super->s_first_data_block;
+       for (i = 0; i < fs->group_desc_count; i++) {
+
+               /* Check superblocks/block group descriptros */
+               if (ext2fs_bg_has_super(fs, i)) {
+                       if (test_block >= block &&
+                           (test_block <= block + fs->desc_blocks))
+                               return 1;
+               }
+
+               /* Check the inode table */
+               if ((fs->group_desc[i].bg_inode_table) &&
+                   (test_block >= fs->group_desc[i].bg_inode_table) &&
+                   (test_block < (fs->group_desc[i].bg_inode_table +
+                                  fs->inode_blocks_per_group)))
+                       return 1;
+
+               /* Check the bitmap blocks */
+               if ((test_block == fs->group_desc[i].bg_block_bitmap) ||
+                   (test_block == fs->group_desc[i].bg_inode_bitmap))
+                       return 1;
+
+               block += fs->super->s_blocks_per_group;
+       }
+       return 0;
+}
+/*
+ * pass2.c --- check directory structure
+ *
+ * Pass 2 of e2fsck iterates through all active directory inodes, and
+ * applies to following tests to each directory entry in the directory
+ * blocks in the inodes:
+ *
+ *      - The length of the directory entry (rec_len) should be at
+ *              least 8 bytes, and no more than the remaining space
+ *              left in the directory block.
+ *      - The length of the name in the directory entry (name_len)
+ *              should be less than (rec_len - 8).
+ *      - The inode number in the directory entry should be within
+ *              legal bounds.
+ *      - The inode number should refer to a in-use inode.
+ *      - The first entry should be '.', and its inode should be
+ *              the inode of the directory.
+ *      - The second entry should be '..'.
+ *
+ * To minimize disk seek time, the directory blocks are processed in
+ * sorted order of block numbers.
+ *
+ * Pass 2 also collects the following information:
+ *      - The inode numbers of the subdirectories for each directory.
+ *
+ * Pass 2 relies on the following information from previous passes:
+ *      - The directory information collected in pass 1.
+ *      - The inode_used_map bitmap
+ *      - The inode_bad_map bitmap
+ *      - The inode_dir_map bitmap
+ *
+ * Pass 2 frees the following data structures
+ *      - The inode_bad_map bitmap
+ *      - The inode_reg_map bitmap
+ */
+
+/*
+ * Keeps track of how many times an inode is referenced.
+ */
+static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf);
+static int check_dir_block(ext2_filsys fs,
+                          struct ext2_db_entry *dir_blocks_info,
+                          void *priv_data);
+static int allocate_dir_block(e2fsck_t ctx, struct ext2_db_entry *dir_blocks_info,
+                             struct problem_context *pctx);
+static int update_dir_block(ext2_filsys fs,
+                           blk_t       *block_nr,
+                           e2_blkcnt_t blockcnt,
+                           blk_t       ref_block,
+                           int         ref_offset,
+                           void        *priv_data);
+static void clear_htree(e2fsck_t ctx, ext2_ino_t ino);
+static int htree_depth(struct dx_dir_info *dx_dir,
+                      struct dx_dirblock_info *dx_db);
+static int special_dir_block_cmp(const void *a, const void *b);
+
+struct check_dir_struct {
+       char *buf;
+       struct problem_context  pctx;
+       int     count, max;
+       e2fsck_t ctx;
+};
+
+static void e2fsck_pass2(e2fsck_t ctx)
+{
+       struct ext2_super_block *sb = ctx->fs->super;
+       struct problem_context  pctx;
+       ext2_filsys             fs = ctx->fs;
+       char                    *buf;
+       struct dir_info         *dir;
+       struct check_dir_struct cd;
+       struct dx_dir_info      *dx_dir;
+       struct dx_dirblock_info *dx_db, *dx_parent;
+       int                     b;
+       int                     i, depth;
+       problem_t               code;
+       int                     bad_dir;
+
+       clear_problem_context(&cd.pctx);
+
+       /* Pass 2 */
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               fix_problem(ctx, PR_2_PASS_HEADER, &cd.pctx);
+
+       cd.pctx.errcode = ext2fs_create_icount2(fs, EXT2_ICOUNT_OPT_INCREMENT,
+                                               0, ctx->inode_link_info,
+                                               &ctx->inode_count);
+       if (cd.pctx.errcode) {
+               fix_problem(ctx, PR_2_ALLOCATE_ICOUNT, &cd.pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+       buf = (char *) e2fsck_allocate_memory(ctx, 2*fs->blocksize,
+                                             "directory scan buffer");
+
+       /*
+        * Set up the parent pointer for the root directory, if
+        * present.  (If the root directory is not present, we will
+        * create it in pass 3.)
+        */
+       dir = e2fsck_get_dir_info(ctx, EXT2_ROOT_INO);
+       if (dir)
+               dir->parent = EXT2_ROOT_INO;
+
+       cd.buf = buf;
+       cd.ctx = ctx;
+       cd.count = 1;
+       cd.max = ext2fs_dblist_count(fs->dblist);
+
+       if (ctx->progress)
+               (void) (ctx->progress)(ctx, 2, 0, cd.max);
+
+       if (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX)
+               ext2fs_dblist_sort(fs->dblist, special_dir_block_cmp);
+
+       cd.pctx.errcode = ext2fs_dblist_iterate(fs->dblist, check_dir_block,
+                                               &cd);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               return;
+       if (cd.pctx.errcode) {
+               fix_problem(ctx, PR_2_DBLIST_ITERATE, &cd.pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+#ifdef ENABLE_HTREE
+       for (i=0; (dx_dir = e2fsck_dx_dir_info_iter(ctx, &i)) != 0;) {
+               if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                       return;
+               if (dx_dir->numblocks == 0)
+                       continue;
+               clear_problem_context(&pctx);
+               bad_dir = 0;
+               pctx.dir = dx_dir->ino;
+               dx_db = dx_dir->dx_block;
+               if (dx_db->flags & DX_FLAG_REFERENCED)
+                       dx_db->flags |= DX_FLAG_DUP_REF;
+               else
+                       dx_db->flags |= DX_FLAG_REFERENCED;
+               /*
+                * Find all of the first and last leaf blocks, and
+                * update their parent's min and max hash values
+                */
+               for (b=0, dx_db = dx_dir->dx_block;
+                    b < dx_dir->numblocks;
+                    b++, dx_db++) {
+                       if ((dx_db->type != DX_DIRBLOCK_LEAF) ||
+                           !(dx_db->flags & (DX_FLAG_FIRST | DX_FLAG_LAST)))
+                               continue;
+                       dx_parent = &dx_dir->dx_block[dx_db->parent];
+                       /*
+                        * XXX Make sure dx_parent->min_hash > dx_db->min_hash
+                        */
+                       if (dx_db->flags & DX_FLAG_FIRST)
+                               dx_parent->min_hash = dx_db->min_hash;
+                       /*
+                        * XXX Make sure dx_parent->max_hash < dx_db->max_hash
+                        */
+                       if (dx_db->flags & DX_FLAG_LAST)
+                               dx_parent->max_hash = dx_db->max_hash;
+               }
+
+               for (b=0, dx_db = dx_dir->dx_block;
+                    b < dx_dir->numblocks;
+                    b++, dx_db++) {
+                       pctx.blkcount = b;
+                       pctx.group = dx_db->parent;
+                       code = 0;
+                       if (!(dx_db->flags & DX_FLAG_FIRST) &&
+                           (dx_db->min_hash < dx_db->node_min_hash)) {
+                               pctx.blk = dx_db->min_hash;
+                               pctx.blk2 = dx_db->node_min_hash;
+                               code = PR_2_HTREE_MIN_HASH;
+                               fix_problem(ctx, code, &pctx);
+                               bad_dir++;
+                       }
+                       if (dx_db->type == DX_DIRBLOCK_LEAF) {
+                               depth = htree_depth(dx_dir, dx_db);
+                               if (depth != dx_dir->depth) {
+                                       code = PR_2_HTREE_BAD_DEPTH;
+                                       fix_problem(ctx, code, &pctx);
+                                       bad_dir++;
+                               }
+                       }
+                       /*
+                        * This test doesn't apply for the root block
+                        * at block #0
+                        */
+                       if (b &&
+                           (dx_db->max_hash > dx_db->node_max_hash)) {
+                               pctx.blk = dx_db->max_hash;
+                               pctx.blk2 = dx_db->node_max_hash;
+                               code = PR_2_HTREE_MAX_HASH;
+                               fix_problem(ctx, code, &pctx);
+                               bad_dir++;
+                       }
+                       if (!(dx_db->flags & DX_FLAG_REFERENCED)) {
+                               code = PR_2_HTREE_NOTREF;
+                               fix_problem(ctx, code, &pctx);
+                               bad_dir++;
+                       } else if (dx_db->flags & DX_FLAG_DUP_REF) {
+                               code = PR_2_HTREE_DUPREF;
+                               fix_problem(ctx, code, &pctx);
+                               bad_dir++;
+                       }
+                       if (code == 0)
+                               continue;
+               }
+               if (bad_dir && fix_problem(ctx, PR_2_HTREE_CLEAR, &pctx)) {
+                       clear_htree(ctx, dx_dir->ino);
+                       dx_dir->numblocks = 0;
+               }
+       }
+#endif
+       ext2fs_free_mem(&buf);
+       ext2fs_free_dblist(fs->dblist);
+
+       ext2fs_free_inode_bitmap(ctx->inode_bad_map);
+       ctx->inode_bad_map = 0;
+       ext2fs_free_inode_bitmap(ctx->inode_reg_map);
+       ctx->inode_reg_map = 0;
+
+       clear_problem_context(&pctx);
+       if (ctx->large_files) {
+               if (!(sb->s_feature_ro_compat &
+                     EXT2_FEATURE_RO_COMPAT_LARGE_FILE) &&
+                   fix_problem(ctx, PR_2_FEATURE_LARGE_FILES, &pctx)) {
+                       sb->s_feature_ro_compat |=
+                               EXT2_FEATURE_RO_COMPAT_LARGE_FILE;
+                       ext2fs_mark_super_dirty(fs);
+               }
+               if (sb->s_rev_level == EXT2_GOOD_OLD_REV &&
+                   fix_problem(ctx, PR_1_FS_REV_LEVEL, &pctx)) {
+                       ext2fs_update_dynamic_rev(fs);
+                       ext2fs_mark_super_dirty(fs);
+               }
+       } else if (!ctx->large_files &&
+           (sb->s_feature_ro_compat &
+             EXT2_FEATURE_RO_COMPAT_LARGE_FILE)) {
+               if (fs->flags & EXT2_FLAG_RW) {
+                       sb->s_feature_ro_compat &=
+                               ~EXT2_FEATURE_RO_COMPAT_LARGE_FILE;
+                       ext2fs_mark_super_dirty(fs);
+               }
+       }
+
+}
+
+#define MAX_DEPTH 32000
+static int htree_depth(struct dx_dir_info *dx_dir,
+                      struct dx_dirblock_info *dx_db)
+{
+       int     depth = 0;
+
+       while (dx_db->type != DX_DIRBLOCK_ROOT && depth < MAX_DEPTH) {
+               dx_db = &dx_dir->dx_block[dx_db->parent];
+               depth++;
+       }
+       return depth;
+}
+
+static int dict_de_cmp(const void *a, const void *b)
+{
+       const struct ext2_dir_entry *de_a, *de_b;
+       int     a_len, b_len;
+
+       de_a = (const struct ext2_dir_entry *) a;
+       a_len = de_a->name_len & 0xFF;
+       de_b = (const struct ext2_dir_entry *) b;
+       b_len = de_b->name_len & 0xFF;
+
+       if (a_len != b_len)
+               return (a_len - b_len);
+
+       return strncmp(de_a->name, de_b->name, a_len);
+}
+
+/*
+ * This is special sort function that makes sure that directory blocks
+ * with a dirblock of zero are sorted to the beginning of the list.
+ * This guarantees that the root node of the htree directories are
+ * processed first, so we know what hash version to use.
+ */
+static int special_dir_block_cmp(const void *a, const void *b)
+{
+       const struct ext2_db_entry *db_a =
+               (const struct ext2_db_entry *) a;
+       const struct ext2_db_entry *db_b =
+               (const struct ext2_db_entry *) b;
+
+       if (db_a->blockcnt && !db_b->blockcnt)
+               return 1;
+
+       if (!db_a->blockcnt && db_b->blockcnt)
+               return -1;
+
+       if (db_a->blk != db_b->blk)
+               return (int) (db_a->blk - db_b->blk);
+
+       if (db_a->ino != db_b->ino)
+               return (int) (db_a->ino - db_b->ino);
+
+       return (int) (db_a->blockcnt - db_b->blockcnt);
+}
+
+
+/*
+ * Make sure the first entry in the directory is '.', and that the
+ * directory entry is sane.
+ */
+static int check_dot(e2fsck_t ctx,
+                    struct ext2_dir_entry *dirent,
+                    ext2_ino_t ino, struct problem_context *pctx)
+{
+       struct ext2_dir_entry *nextdir;
+       int     status = 0;
+       int     created = 0;
+       int     new_len;
+       int     problem = 0;
+
+       if (!dirent->inode)
+               problem = PR_2_MISSING_DOT;
+       else if (((dirent->name_len & 0xFF) != 1) ||
+                (dirent->name[0] != '.'))
+               problem = PR_2_1ST_NOT_DOT;
+       else if (dirent->name[1] != '\0')
+               problem = PR_2_DOT_NULL_TERM;
+
+       if (problem) {
+               if (fix_problem(ctx, problem, pctx)) {
+                       if (dirent->rec_len < 12)
+                               dirent->rec_len = 12;
+                       dirent->inode = ino;
+                       dirent->name_len = 1;
+                       dirent->name[0] = '.';
+                       dirent->name[1] = '\0';
+                       status = 1;
+                       created = 1;
+               }
+       }
+       if (dirent->inode != ino) {
+               if (fix_problem(ctx, PR_2_BAD_INODE_DOT, pctx)) {
+                       dirent->inode = ino;
+                       status = 1;
+               }
+       }
+       if (dirent->rec_len > 12) {
+               new_len = dirent->rec_len - 12;
+               if (new_len > 12) {
+                       if (created ||
+                           fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) {
+                               nextdir = (struct ext2_dir_entry *)
+                                       ((char *) dirent + 12);
+                               dirent->rec_len = 12;
+                               nextdir->rec_len = new_len;
+                               nextdir->inode = 0;
+                               nextdir->name_len = 0;
+                               status = 1;
+                       }
+               }
+       }
+       return status;
+}
+
+/*
+ * Make sure the second entry in the directory is '..', and that the
+ * directory entry is sane.  We do not check the inode number of '..'
+ * here; this gets done in pass 3.
+ */
+static int check_dotdot(e2fsck_t ctx,
+                       struct ext2_dir_entry *dirent,
+                       struct dir_info *dir, struct problem_context *pctx)
+{
+       int             problem = 0;
+
+       if (!dirent->inode)
+               problem = PR_2_MISSING_DOT_DOT;
+       else if (((dirent->name_len & 0xFF) != 2) ||
+                (dirent->name[0] != '.') ||
+                (dirent->name[1] != '.'))
+               problem = PR_2_2ND_NOT_DOT_DOT;
+       else if (dirent->name[2] != '\0')
+               problem = PR_2_DOT_DOT_NULL_TERM;
+
+       if (problem) {
+               if (fix_problem(ctx, problem, pctx)) {
+                       if (dirent->rec_len < 12)
+                               dirent->rec_len = 12;
+                       /*
+                        * Note: we don't have the parent inode just
+                        * yet, so we will fill it in with the root
+                        * inode.  This will get fixed in pass 3.
+                        */
+                       dirent->inode = EXT2_ROOT_INO;
+                       dirent->name_len = 2;
+                       dirent->name[0] = '.';
+                       dirent->name[1] = '.';
+                       dirent->name[2] = '\0';
+                       return 1;
+               }
+               return 0;
+       }
+       dir->dotdot = dirent->inode;
+       return 0;
+}
+
+/*
+ * Check to make sure a directory entry doesn't contain any illegal
+ * characters.
+ */
+static int check_name(e2fsck_t ctx,
+                     struct ext2_dir_entry *dirent,
+                     struct problem_context *pctx)
+{
+       int     i;
+       int     fixup = -1;
+       int     ret = 0;
+
+       for ( i = 0; i < (dirent->name_len & 0xFF); i++) {
+               if (dirent->name[i] == '/' || dirent->name[i] == '\0') {
+                       if (fixup < 0) {
+                               fixup = fix_problem(ctx, PR_2_BAD_NAME, pctx);
+                       }
+                       if (fixup) {
+                               dirent->name[i] = '.';
+                               ret = 1;
+                       }
+               }
+       }
+       return ret;
+}
+
+/*
+ * Check the directory filetype (if present)
+ */
+
+/*
+ * Given a mode, return the ext2 file type
+ */
+static int ext2_file_type(unsigned int mode)
+{
+       if (LINUX_S_ISREG(mode))
+               return EXT2_FT_REG_FILE;
+
+       if (LINUX_S_ISDIR(mode))
+               return EXT2_FT_DIR;
+
+       if (LINUX_S_ISCHR(mode))
+               return EXT2_FT_CHRDEV;
+
+       if (LINUX_S_ISBLK(mode))
+               return EXT2_FT_BLKDEV;
+
+       if (LINUX_S_ISLNK(mode))
+               return EXT2_FT_SYMLINK;
+
+       if (LINUX_S_ISFIFO(mode))
+               return EXT2_FT_FIFO;
+
+       if (LINUX_S_ISSOCK(mode))
+               return EXT2_FT_SOCK;
+
+       return 0;
+}
+
+static int check_filetype(e2fsck_t ctx,
+                                  struct ext2_dir_entry *dirent,
+                                  struct problem_context *pctx)
+{
+       int     filetype = dirent->name_len >> 8;
+       int     should_be = EXT2_FT_UNKNOWN;
+       struct ext2_inode       inode;
+
+       if (!(ctx->fs->super->s_feature_incompat &
+             EXT2_FEATURE_INCOMPAT_FILETYPE)) {
+               if (filetype == 0 ||
+                   !fix_problem(ctx, PR_2_CLEAR_FILETYPE, pctx))
+                       return 0;
+               dirent->name_len = dirent->name_len & 0xFF;
+               return 1;
+       }
+
+       if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, dirent->inode)) {
+               should_be = EXT2_FT_DIR;
+       } else if (ext2fs_test_inode_bitmap(ctx->inode_reg_map,
+                                           dirent->inode)) {
+               should_be = EXT2_FT_REG_FILE;
+       } else if (ctx->inode_bad_map &&
+                  ext2fs_test_inode_bitmap(ctx->inode_bad_map,
+                                           dirent->inode))
+               should_be = 0;
+       else {
+               e2fsck_read_inode(ctx, dirent->inode, &inode,
+                                 "check_filetype");
+               should_be = ext2_file_type(inode.i_mode);
+       }
+       if (filetype == should_be)
+               return 0;
+       pctx->num = should_be;
+
+       if (fix_problem(ctx, filetype ? PR_2_BAD_FILETYPE : PR_2_SET_FILETYPE,
+                       pctx) == 0)
+               return 0;
+
+       dirent->name_len = (dirent->name_len & 0xFF) | should_be << 8;
+       return 1;
+}
+
+#ifdef ENABLE_HTREE
+static void parse_int_node(ext2_filsys fs,
+                          struct ext2_db_entry *db,
+                          struct check_dir_struct *cd,
+                          struct dx_dir_info   *dx_dir,
+                          char *block_buf)
+{
+       struct          ext2_dx_root_info  *root;
+       struct          ext2_dx_entry *ent;
+       struct          ext2_dx_countlimit *limit;
+       struct dx_dirblock_info *dx_db;
+       int             i, expect_limit, count;
+       blk_t           blk;
+       ext2_dirhash_t  min_hash = 0xffffffff;
+       ext2_dirhash_t  max_hash = 0;
+       ext2_dirhash_t  hash = 0, prev_hash;
+
+       if (db->blockcnt == 0) {
+               root = (struct ext2_dx_root_info *) (block_buf + 24);
+               ent = (struct ext2_dx_entry *) (block_buf + 24 + root->info_length);
+       } else {
+               ent = (struct ext2_dx_entry *) (block_buf+8);
+       }
+       limit = (struct ext2_dx_countlimit *) ent;
+
+       count = ext2fs_le16_to_cpu(limit->count);
+       expect_limit = (fs->blocksize - ((char *) ent - block_buf)) /
+               sizeof(struct ext2_dx_entry);
+       if (ext2fs_le16_to_cpu(limit->limit) != expect_limit) {
+               cd->pctx.num = ext2fs_le16_to_cpu(limit->limit);
+               if (fix_problem(cd->ctx, PR_2_HTREE_BAD_LIMIT, &cd->pctx))
+                       goto clear_and_exit;
+       }
+       if (count > expect_limit) {
+               cd->pctx.num = count;
+               if (fix_problem(cd->ctx, PR_2_HTREE_BAD_COUNT, &cd->pctx))
+                       goto clear_and_exit;
+               count = expect_limit;
+       }
+
+       for (i=0; i < count; i++) {
+               prev_hash = hash;
+               hash = i ? (ext2fs_le32_to_cpu(ent[i].hash) & ~1) : 0;
+               blk = ext2fs_le32_to_cpu(ent[i].block) & 0x0ffffff;
+               /* Check to make sure the block is valid */
+               if (blk > (blk_t) dx_dir->numblocks) {
+                       cd->pctx.blk = blk;
+                       if (fix_problem(cd->ctx, PR_2_HTREE_BADBLK,
+                                       &cd->pctx))
+                               goto clear_and_exit;
+               }
+               if (hash < prev_hash &&
+                   fix_problem(cd->ctx, PR_2_HTREE_HASH_ORDER, &cd->pctx))
+                       goto clear_and_exit;
+               dx_db = &dx_dir->dx_block[blk];
+               if (dx_db->flags & DX_FLAG_REFERENCED) {
+                       dx_db->flags |= DX_FLAG_DUP_REF;
+               } else {
+                       dx_db->flags |= DX_FLAG_REFERENCED;
+                       dx_db->parent = db->blockcnt;
+               }
+               if (hash < min_hash)
+                       min_hash = hash;
+               if (hash > max_hash)
+                       max_hash = hash;
+               dx_db->node_min_hash = hash;
+               if ((i+1) < count)
+                       dx_db->node_max_hash =
+                         ext2fs_le32_to_cpu(ent[i+1].hash) & ~1;
+               else {
+                       dx_db->node_max_hash = 0xfffffffe;
+                       dx_db->flags |= DX_FLAG_LAST;
+               }
+               if (i == 0)
+                       dx_db->flags |= DX_FLAG_FIRST;
+       }
+       dx_db = &dx_dir->dx_block[db->blockcnt];
+       dx_db->min_hash = min_hash;
+       dx_db->max_hash = max_hash;
+       return;
+
+clear_and_exit:
+       clear_htree(cd->ctx, cd->pctx.ino);
+       dx_dir->numblocks = 0;
+}
+#endif /* ENABLE_HTREE */
+
+/*
+ * Given a busted directory, try to salvage it somehow.
+ *
+ */
+static void salvage_directory(ext2_filsys fs,
+                             struct ext2_dir_entry *dirent,
+                             struct ext2_dir_entry *prev,
+                             unsigned int *offset)
+{
+       char    *cp = (char *) dirent;
+       int left = fs->blocksize - *offset - dirent->rec_len;
+       int name_len = dirent->name_len & 0xFF;
+
+       /*
+        * Special case of directory entry of size 8: copy what's left
+        * of the directory block up to cover up the invalid hole.
+        */
+       if ((left >= 12) && (dirent->rec_len == 8)) {
+               memmove(cp, cp+8, left);
+               memset(cp + left, 0, 8);
+               return;
+       }
+       /*
+        * If the directory entry overruns the end of the directory
+        * block, and the name is small enough to fit, then adjust the
+        * record length.
+        */
+       if ((left < 0) &&
+           (name_len + 8 <= dirent->rec_len + left) &&
+           dirent->inode <= fs->super->s_inodes_count &&
+           strnlen(dirent->name, name_len) == name_len) {
+               dirent->rec_len += left;
+               return;
+       }
+       /*
+        * If the directory entry is a multiple of four, so it is
+        * valid, let the previous directory entry absorb the invalid
+        * one.
+        */
+       if (prev && dirent->rec_len && (dirent->rec_len % 4) == 0) {
+               prev->rec_len += dirent->rec_len;
+               *offset += dirent->rec_len;
+               return;
+       }
+       /*
+        * Default salvage method --- kill all of the directory
+        * entries for the rest of the block.  We will either try to
+        * absorb it into the previous directory entry, or create a
+        * new empty directory entry the rest of the directory block.
+        */
+       if (prev) {
+               prev->rec_len += fs->blocksize - *offset;
+               *offset = fs->blocksize;
+       } else {
+               dirent->rec_len = fs->blocksize - *offset;
+               dirent->name_len = 0;
+               dirent->inode = 0;
+       }
+}
+
+static int check_dir_block(ext2_filsys fs,
+                          struct ext2_db_entry *db,
+                          void *priv_data)
+{
+       struct dir_info         *subdir, *dir;
+       struct dx_dir_info      *dx_dir;
+#ifdef ENABLE_HTREE
+       struct dx_dirblock_info *dx_db = 0;
+#endif /* ENABLE_HTREE */
+       struct ext2_dir_entry   *dirent, *prev;
+       ext2_dirhash_t          hash;
+       unsigned int            offset = 0;
+       int                     dir_modified = 0;
+       int                     dot_state;
+       blk_t                   block_nr = db->blk;
+       ext2_ino_t              ino = db->ino;
+       __u16                   links;
+       struct check_dir_struct *cd;
+       char                    *buf;
+       e2fsck_t                ctx;
+       int                     problem;
+       struct ext2_dx_root_info *root;
+       struct ext2_dx_countlimit *limit;
+       static dict_t de_dict;
+       struct problem_context  pctx;
+       int     dups_found = 0;
+
+       cd = (struct check_dir_struct *) priv_data;
+       buf = cd->buf;
+       ctx = cd->ctx;
+
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               return DIRENT_ABORT;
+
+       if (ctx->progress && (ctx->progress)(ctx, 2, cd->count++, cd->max))
+               return DIRENT_ABORT;
+
+       /*
+        * Make sure the inode is still in use (could have been
+        * deleted in the duplicate/bad blocks pass.
+        */
+       if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, ino)))
+               return 0;
+
+       cd->pctx.ino = ino;
+       cd->pctx.blk = block_nr;
+       cd->pctx.blkcount = db->blockcnt;
+       cd->pctx.ino2 = 0;
+       cd->pctx.dirent = 0;
+       cd->pctx.num = 0;
+
+       if (db->blk == 0) {
+               if (allocate_dir_block(ctx, db, &cd->pctx))
+                       return 0;
+               block_nr = db->blk;
+       }
+
+       if (db->blockcnt)
+               dot_state = 2;
+       else
+               dot_state = 0;
+
+       if (ctx->dirs_to_hash &&
+           ext2fs_u32_list_test(ctx->dirs_to_hash, ino))
+               dups_found++;
+
+       cd->pctx.errcode = ext2fs_read_dir_block(fs, block_nr, buf);
+       if (cd->pctx.errcode == EXT2_ET_DIR_CORRUPTED)
+               cd->pctx.errcode = 0; /* We'll handle this ourselves */
+       if (cd->pctx.errcode) {
+               if (!fix_problem(ctx, PR_2_READ_DIRBLOCK, &cd->pctx)) {
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return DIRENT_ABORT;
+               }
+               memset(buf, 0, fs->blocksize);
+       }
+#ifdef ENABLE_HTREE
+       dx_dir = e2fsck_get_dx_dir_info(ctx, ino);
+       if (dx_dir && dx_dir->numblocks) {
+               if (db->blockcnt >= dx_dir->numblocks) {
+                       printf("XXX should never happen!!!\n");
+                       abort();
+               }
+               dx_db = &dx_dir->dx_block[db->blockcnt];
+               dx_db->type = DX_DIRBLOCK_LEAF;
+               dx_db->phys = block_nr;
+               dx_db->min_hash = ~0;
+               dx_db->max_hash = 0;
+
+               dirent = (struct ext2_dir_entry *) buf;
+               limit = (struct ext2_dx_countlimit *) (buf+8);
+               if (db->blockcnt == 0) {
+                       root = (struct ext2_dx_root_info *) (buf + 24);
+                       dx_db->type = DX_DIRBLOCK_ROOT;
+                       dx_db->flags |= DX_FLAG_FIRST | DX_FLAG_LAST;
+                       if ((root->reserved_zero ||
+                            root->info_length < 8 ||
+                            root->indirect_levels > 1) &&
+                           fix_problem(ctx, PR_2_HTREE_BAD_ROOT, &cd->pctx)) {
+                               clear_htree(ctx, ino);
+                               dx_dir->numblocks = 0;
+                               dx_db = 0;
+                       }
+                       dx_dir->hashversion = root->hash_version;
+                       dx_dir->depth = root->indirect_levels + 1;
+               } else if ((dirent->inode == 0) &&
+                          (dirent->rec_len == fs->blocksize) &&
+                          (dirent->name_len == 0) &&
+                          (ext2fs_le16_to_cpu(limit->limit) ==
+                           ((fs->blocksize-8) /
+                            sizeof(struct ext2_dx_entry))))
+                       dx_db->type = DX_DIRBLOCK_NODE;
+       }
+#endif /* ENABLE_HTREE */
+
+       dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp);
+       prev = 0;
+       do {
+               problem = 0;
+               dirent = (struct ext2_dir_entry *) (buf + offset);
+               cd->pctx.dirent = dirent;
+               cd->pctx.num = offset;
+               if (((offset + dirent->rec_len) > fs->blocksize) ||
+                   (dirent->rec_len < 12) ||
+                   ((dirent->rec_len % 4) != 0) ||
+                   (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+                       if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) {
+                               salvage_directory(fs, dirent, prev, &offset);
+                               dir_modified++;
+                               continue;
+                       } else
+                               goto abort_free_dict;
+               }
+               if ((dirent->name_len & 0xFF) > EXT2_NAME_LEN) {
+                       if (fix_problem(ctx, PR_2_FILENAME_LONG, &cd->pctx)) {
+                               dirent->name_len = EXT2_NAME_LEN;
+                               dir_modified++;
+                       }
+               }
+
+               if (dot_state == 0) {
+                       if (check_dot(ctx, dirent, ino, &cd->pctx))
+                               dir_modified++;
+               } else if (dot_state == 1) {
+                       dir = e2fsck_get_dir_info(ctx, ino);
+                       if (!dir) {
+                               fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx);
+                               goto abort_free_dict;
+                       }
+                       if (check_dotdot(ctx, dirent, dir, &cd->pctx))
+                               dir_modified++;
+               } else if (dirent->inode == ino) {
+                       problem = PR_2_LINK_DOT;
+                       if (fix_problem(ctx, PR_2_LINK_DOT, &cd->pctx)) {
+                               dirent->inode = 0;
+                               dir_modified++;
+                               goto next;
+                       }
+               }
+               if (!dirent->inode)
+                       goto next;
+
+               /*
+                * Make sure the inode listed is a legal one.
+                */
+               if (((dirent->inode != EXT2_ROOT_INO) &&
+                    (dirent->inode < EXT2_FIRST_INODE(fs->super))) ||
+                   (dirent->inode > fs->super->s_inodes_count)) {
+                       problem = PR_2_BAD_INO;
+               } else if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map,
+                                              dirent->inode))) {
+                       /*
+                        * If the inode is unused, offer to clear it.
+                        */
+                       problem = PR_2_UNUSED_INODE;
+               } else if ((dot_state > 1) &&
+                          ((dirent->name_len & 0xFF) == 1) &&
+                          (dirent->name[0] == '.')) {
+                       /*
+                        * If there's a '.' entry in anything other
+                        * than the first directory entry, it's a
+                        * duplicate entry that should be removed.
+                        */
+                       problem = PR_2_DUP_DOT;
+               } else if ((dot_state > 1) &&
+                          ((dirent->name_len & 0xFF) == 2) &&
+                          (dirent->name[0] == '.') &&
+                          (dirent->name[1] == '.')) {
+                       /*
+                        * If there's a '..' entry in anything other
+                        * than the second directory entry, it's a
+                        * duplicate entry that should be removed.
+                        */
+                       problem = PR_2_DUP_DOT_DOT;
+               } else if ((dot_state > 1) &&
+                          (dirent->inode == EXT2_ROOT_INO)) {
+                       /*
+                        * Don't allow links to the root directory.
+                        * We check this specially to make sure we
+                        * catch this error case even if the root
+                        * directory hasn't been created yet.
+                        */
+                       problem = PR_2_LINK_ROOT;
+               } else if ((dot_state > 1) &&
+                          (dirent->name_len & 0xFF) == 0) {
+                       /*
+                        * Don't allow zero-length directory names.
+                        */
+                       problem = PR_2_NULL_NAME;
+               }
+
+               if (problem) {
+                       if (fix_problem(ctx, problem, &cd->pctx)) {
+                               dirent->inode = 0;
+                               dir_modified++;
+                               goto next;
+                       } else {
+                               ext2fs_unmark_valid(fs);
+                               if (problem == PR_2_BAD_INO)
+                                       goto next;
+                       }
+               }
+
+               /*
+                * If the inode was marked as having bad fields in
+                * pass1, process it and offer to fix/clear it.
+                * (We wait until now so that we can display the
+                * pathname to the user.)
+                */
+               if (ctx->inode_bad_map &&
+                   ext2fs_test_inode_bitmap(ctx->inode_bad_map,
+                                            dirent->inode)) {
+                       if (e2fsck_process_bad_inode(ctx, ino,
+                                                    dirent->inode,
+                                                    buf + fs->blocksize)) {
+                               dirent->inode = 0;
+                               dir_modified++;
+                               goto next;
+                       }
+                       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                               return DIRENT_ABORT;
+               }
+
+               if (check_name(ctx, dirent, &cd->pctx))
+                       dir_modified++;
+
+               if (check_filetype(ctx, dirent, &cd->pctx))
+                       dir_modified++;
+
+#ifdef ENABLE_HTREE
+               if (dx_db) {
+                       ext2fs_dirhash(dx_dir->hashversion, dirent->name,
+                                      (dirent->name_len & 0xFF),
+                                      fs->super->s_hash_seed, &hash, 0);
+                       if (hash < dx_db->min_hash)
+                               dx_db->min_hash = hash;
+                       if (hash > dx_db->max_hash)
+                               dx_db->max_hash = hash;
+               }
+#endif
+
+               /*
+                * If this is a directory, then mark its parent in its
+                * dir_info structure.  If the parent field is already
+                * filled in, then this directory has more than one
+                * hard link.  We assume the first link is correct,
+                * and ask the user if he/she wants to clear this one.
+                */
+               if ((dot_state > 1) &&
+                   (ext2fs_test_inode_bitmap(ctx->inode_dir_map,
+                                             dirent->inode))) {
+                       subdir = e2fsck_get_dir_info(ctx, dirent->inode);
+                       if (!subdir) {
+                               cd->pctx.ino = dirent->inode;
+                               fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx);
+                               goto abort_free_dict;
+                       }
+                       if (subdir->parent) {
+                               cd->pctx.ino2 = subdir->parent;
+                               if (fix_problem(ctx, PR_2_LINK_DIR,
+                                               &cd->pctx)) {
+                                       dirent->inode = 0;
+                                       dir_modified++;
+                                       goto next;
+                               }
+                               cd->pctx.ino2 = 0;
+                       } else
+                               subdir->parent = ino;
+               }
+
+               if (dups_found) {
+                       ;
+               } else if (dict_lookup(&de_dict, dirent)) {
+                       clear_problem_context(&pctx);
+                       pctx.ino = ino;
+                       pctx.dirent = dirent;
+                       fix_problem(ctx, PR_2_REPORT_DUP_DIRENT, &pctx);
+                       if (!ctx->dirs_to_hash)
+                               ext2fs_u32_list_create(&ctx->dirs_to_hash, 50);
+                       if (ctx->dirs_to_hash)
+                               ext2fs_u32_list_add(ctx->dirs_to_hash, ino);
+                       dups_found++;
+               } else
+                       dict_alloc_insert(&de_dict, dirent, dirent);
+
+               ext2fs_icount_increment(ctx->inode_count, dirent->inode,
+                                       &links);
+               if (links > 1)
+                       ctx->fs_links_count++;
+               ctx->fs_total_count++;
+       next:
+               prev = dirent;
+               offset += dirent->rec_len;
+               dot_state++;
+       } while (offset < fs->blocksize);
+#ifdef ENABLE_HTREE
+       if (dx_db) {
+               cd->pctx.dir = cd->pctx.ino;
+               if ((dx_db->type == DX_DIRBLOCK_ROOT) ||
+                   (dx_db->type == DX_DIRBLOCK_NODE))
+                       parse_int_node(fs, db, cd, dx_dir, buf);
+       }
+#endif /* ENABLE_HTREE */
+       if (offset != fs->blocksize) {
+               cd->pctx.num = dirent->rec_len - fs->blocksize + offset;
+               if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) {
+                       dirent->rec_len = cd->pctx.num;
+                       dir_modified++;
+               }
+       }
+       if (dir_modified) {
+               cd->pctx.errcode = ext2fs_write_dir_block(fs, block_nr, buf);
+               if (cd->pctx.errcode) {
+                       if (!fix_problem(ctx, PR_2_WRITE_DIRBLOCK,
+                                        &cd->pctx))
+                               goto abort_free_dict;
+               }
+               ext2fs_mark_changed(fs);
+       }
+       dict_free_nodes(&de_dict);
+       return 0;
+abort_free_dict:
+       dict_free_nodes(&de_dict);
+       ctx->flags |= E2F_FLAG_ABORT;
+       return DIRENT_ABORT;
+}
+
+/*
+ * This function is called to deallocate a block, and is an interator
+ * functioned called by deallocate inode via ext2fs_iterate_block().
+ */
+static int deallocate_inode_block(ext2_filsys fs, blk_t *block_nr,
+                                 e2_blkcnt_t blockcnt FSCK_ATTR((unused)),
+                                 blk_t ref_block FSCK_ATTR((unused)),
+                                 int ref_offset FSCK_ATTR((unused)),
+                                 void *priv_data)
+{
+       e2fsck_t        ctx = (e2fsck_t) priv_data;
+
+       if (HOLE_BLKADDR(*block_nr))
+               return 0;
+       if ((*block_nr < fs->super->s_first_data_block) ||
+           (*block_nr >= fs->super->s_blocks_count))
+               return 0;
+       ext2fs_unmark_block_bitmap(ctx->block_found_map, *block_nr);
+       ext2fs_block_alloc_stats(fs, *block_nr, -1);
+       return 0;
+}
+
+/*
+ * This fuction deallocates an inode
+ */
+static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf)
+{
+       ext2_filsys fs = ctx->fs;
+       struct ext2_inode       inode;
+       struct problem_context  pctx;
+       __u32                   count;
+
+       ext2fs_icount_store(ctx->inode_link_info, ino, 0);
+       e2fsck_read_inode(ctx, ino, &inode, "deallocate_inode");
+       inode.i_links_count = 0;
+       inode.i_dtime = time(0);
+       e2fsck_write_inode(ctx, ino, &inode, "deallocate_inode");
+       clear_problem_context(&pctx);
+       pctx.ino = ino;
+
+       /*
+        * Fix up the bitmaps...
+        */
+       e2fsck_read_bitmaps(ctx);
+       ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino);
+       ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino);
+       if (ctx->inode_bad_map)
+               ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino);
+       ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode));
+
+       if (inode.i_file_acl &&
+           (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+               pctx.errcode = ext2fs_adjust_ea_refcount(fs, inode.i_file_acl,
+                                                  block_buf, -1, &count);
+               if (pctx.errcode == EXT2_ET_BAD_EA_BLOCK_NUM) {
+                       pctx.errcode = 0;
+                       count = 1;
+               }
+               if (pctx.errcode) {
+                       pctx.blk = inode.i_file_acl;
+                       fix_problem(ctx, PR_2_ADJ_EA_REFCOUNT, &pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+               if (count == 0) {
+                       ext2fs_unmark_block_bitmap(ctx->block_found_map,
+                                                  inode.i_file_acl);
+                       ext2fs_block_alloc_stats(fs, inode.i_file_acl, -1);
+               }
+               inode.i_file_acl = 0;
+       }
+
+       if (!ext2fs_inode_has_valid_blocks(&inode))
+               return;
+
+       if (LINUX_S_ISREG(inode.i_mode) &&
+           (inode.i_size_high || inode.i_size & 0x80000000UL))
+               ctx->large_files--;
+
+       pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf,
+                                           deallocate_inode_block, ctx);
+       if (pctx.errcode) {
+               fix_problem(ctx, PR_2_DEALLOC_INODE, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+}
+
+/*
+ * This fuction clears the htree flag on an inode
+ */
+static void clear_htree(e2fsck_t ctx, ext2_ino_t ino)
+{
+       struct ext2_inode       inode;
+
+       e2fsck_read_inode(ctx, ino, &inode, "clear_htree");
+       inode.i_flags = inode.i_flags & ~EXT2_INDEX_FL;
+       e2fsck_write_inode(ctx, ino, &inode, "clear_htree");
+       if (ctx->dirs_to_hash)
+               ext2fs_u32_list_add(ctx->dirs_to_hash, ino);
+}
+
+
+static int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir,
+                                   ext2_ino_t ino, char *buf)
+{
+       ext2_filsys fs = ctx->fs;
+       struct ext2_inode       inode;
+       int                     inode_modified = 0;
+       int                     not_fixed = 0;
+       unsigned char           *frag, *fsize;
+       struct problem_context  pctx;
+       int     problem = 0;
+
+       e2fsck_read_inode(ctx, ino, &inode, "process_bad_inode");
+
+       clear_problem_context(&pctx);
+       pctx.ino = ino;
+       pctx.dir = dir;
+       pctx.inode = &inode;
+
+       if (inode.i_file_acl &&
+           !(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR) &&
+           fix_problem(ctx, PR_2_FILE_ACL_ZERO, &pctx)) {
+               inode.i_file_acl = 0;
+#if BB_BIG_ENDIAN
+               /*
+                * This is a special kludge to deal with long symlinks
+                * on big endian systems.  i_blocks had already been
+                * decremented earlier in pass 1, but since i_file_acl
+                * hadn't yet been cleared, ext2fs_read_inode()
+                * assumed that the file was short symlink and would
+                * not have byte swapped i_block[0].  Hence, we have
+                * to byte-swap it here.
+                */
+               if (LINUX_S_ISLNK(inode.i_mode) &&
+                   (fs->flags & EXT2_FLAG_SWAP_BYTES) &&
+                   (inode.i_blocks == fs->blocksize >> 9))
+                       inode.i_block[0] = ext2fs_swab32(inode.i_block[0]);
+#endif
+               inode_modified++;
+       } else
+               not_fixed++;
+
+       if (!LINUX_S_ISDIR(inode.i_mode) && !LINUX_S_ISREG(inode.i_mode) &&
+           !LINUX_S_ISCHR(inode.i_mode) && !LINUX_S_ISBLK(inode.i_mode) &&
+           !LINUX_S_ISLNK(inode.i_mode) && !LINUX_S_ISFIFO(inode.i_mode) &&
+           !(LINUX_S_ISSOCK(inode.i_mode)))
+               problem = PR_2_BAD_MODE;
+       else if (LINUX_S_ISCHR(inode.i_mode)
+                && !e2fsck_pass1_check_device_inode(fs, &inode))
+               problem = PR_2_BAD_CHAR_DEV;
+       else if (LINUX_S_ISBLK(inode.i_mode)
+                && !e2fsck_pass1_check_device_inode(fs, &inode))
+               problem = PR_2_BAD_BLOCK_DEV;
+       else if (LINUX_S_ISFIFO(inode.i_mode)
+                && !e2fsck_pass1_check_device_inode(fs, &inode))
+               problem = PR_2_BAD_FIFO;
+       else if (LINUX_S_ISSOCK(inode.i_mode)
+                && !e2fsck_pass1_check_device_inode(fs, &inode))
+               problem = PR_2_BAD_SOCKET;
+       else if (LINUX_S_ISLNK(inode.i_mode)
+                && !e2fsck_pass1_check_symlink(fs, &inode, buf)) {
+               problem = PR_2_INVALID_SYMLINK;
+       }
+
+       if (problem) {
+               if (fix_problem(ctx, problem, &pctx)) {
+                       deallocate_inode(ctx, ino, 0);
+                       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                               return 0;
+                       return 1;
+               } else
+                       not_fixed++;
+               problem = 0;
+       }
+
+       if (inode.i_faddr) {
+               if (fix_problem(ctx, PR_2_FADDR_ZERO, &pctx)) {
+                       inode.i_faddr = 0;
+                       inode_modified++;
+               } else
+                       not_fixed++;
+       }
+
+       switch (fs->super->s_creator_os) {
+           case EXT2_OS_LINUX:
+               frag = &inode.osd2.linux2.l_i_frag;
+               fsize = &inode.osd2.linux2.l_i_fsize;
+               break;
+           case EXT2_OS_HURD:
+               frag = &inode.osd2.hurd2.h_i_frag;
+               fsize = &inode.osd2.hurd2.h_i_fsize;
+               break;
+           case EXT2_OS_MASIX:
+               frag = &inode.osd2.masix2.m_i_frag;
+               fsize = &inode.osd2.masix2.m_i_fsize;
+               break;
+           default:
+               frag = fsize = 0;
+       }
+       if (frag && *frag) {
+               pctx.num = *frag;
+               if (fix_problem(ctx, PR_2_FRAG_ZERO, &pctx)) {
+                       *frag = 0;
+                       inode_modified++;
+               } else
+                       not_fixed++;
+               pctx.num = 0;
+       }
+       if (fsize && *fsize) {
+               pctx.num = *fsize;
+               if (fix_problem(ctx, PR_2_FSIZE_ZERO, &pctx)) {
+                       *fsize = 0;
+                       inode_modified++;
+               } else
+                       not_fixed++;
+               pctx.num = 0;
+       }
+
+       if (inode.i_file_acl &&
+           ((inode.i_file_acl < fs->super->s_first_data_block) ||
+            (inode.i_file_acl >= fs->super->s_blocks_count))) {
+               if (fix_problem(ctx, PR_2_FILE_ACL_BAD, &pctx)) {
+                       inode.i_file_acl = 0;
+                       inode_modified++;
+               } else
+                       not_fixed++;
+       }
+       if (inode.i_dir_acl &&
+           LINUX_S_ISDIR(inode.i_mode)) {
+               if (fix_problem(ctx, PR_2_DIR_ACL_ZERO, &pctx)) {
+                       inode.i_dir_acl = 0;
+                       inode_modified++;
+               } else
+                       not_fixed++;
+       }
+
+       if (inode_modified)
+               e2fsck_write_inode(ctx, ino, &inode, "process_bad_inode");
+       if (!not_fixed)
+               ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino);
+       return 0;
+}
+
+
+/*
+ * allocate_dir_block --- this function allocates a new directory
+ *      block for a particular inode; this is done if a directory has
+ *      a "hole" in it, or if a directory has a illegal block number
+ *      that was zeroed out and now needs to be replaced.
+ */
+static int allocate_dir_block(e2fsck_t ctx, struct ext2_db_entry *db,
+                             struct problem_context *pctx)
+{
+       ext2_filsys fs = ctx->fs;
+       blk_t                   blk;
+       char                    *block;
+       struct ext2_inode       inode;
+
+       if (fix_problem(ctx, PR_2_DIRECTORY_HOLE, pctx) == 0)
+               return 1;
+
+       /*
+        * Read the inode and block bitmaps in; we'll be messing with
+        * them.
+        */
+       e2fsck_read_bitmaps(ctx);
+
+       /*
+        * First, find a free block
+        */
+       pctx->errcode = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk);
+       if (pctx->errcode) {
+               pctx->str = "ext2fs_new_block";
+               fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx);
+               return 1;
+       }
+       ext2fs_mark_block_bitmap(ctx->block_found_map, blk);
+       ext2fs_mark_block_bitmap(fs->block_map, blk);
+       ext2fs_mark_bb_dirty(fs);
+
+       /*
+        * Now let's create the actual data block for the inode
+        */
+       if (db->blockcnt)
+               pctx->errcode = ext2fs_new_dir_block(fs, 0, 0, &block);
+       else
+               pctx->errcode = ext2fs_new_dir_block(fs, db->ino,
+                                                    EXT2_ROOT_INO, &block);
+
+       if (pctx->errcode) {
+               pctx->str = "ext2fs_new_dir_block";
+               fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx);
+               return 1;
+       }
+
+       pctx->errcode = ext2fs_write_dir_block(fs, blk, block);
+       ext2fs_free_mem(&block);
+       if (pctx->errcode) {
+               pctx->str = "ext2fs_write_dir_block";
+               fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx);
+               return 1;
+       }
+
+       /*
+        * Update the inode block count
+        */
+       e2fsck_read_inode(ctx, db->ino, &inode, "allocate_dir_block");
+       inode.i_blocks += fs->blocksize / 512;
+       if (inode.i_size < (db->blockcnt+1) * fs->blocksize)
+               inode.i_size = (db->blockcnt+1) * fs->blocksize;
+       e2fsck_write_inode(ctx, db->ino, &inode, "allocate_dir_block");
+
+       /*
+        * Finally, update the block pointers for the inode
+        */
+       db->blk = blk;
+       pctx->errcode = ext2fs_block_iterate2(fs, db->ino, BLOCK_FLAG_HOLE,
+                                     0, update_dir_block, db);
+       if (pctx->errcode) {
+               pctx->str = "ext2fs_block_iterate";
+               fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx);
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * This is a helper function for allocate_dir_block().
+ */
+static int update_dir_block(ext2_filsys fs FSCK_ATTR((unused)),
+                           blk_t       *block_nr,
+                           e2_blkcnt_t blockcnt,
+                           blk_t ref_block FSCK_ATTR((unused)),
+                           int ref_offset FSCK_ATTR((unused)),
+                           void *priv_data)
+{
+       struct ext2_db_entry *db;
+
+       db = (struct ext2_db_entry *) priv_data;
+       if (db->blockcnt == (int) blockcnt) {
+               *block_nr = db->blk;
+               return BLOCK_CHANGED;
+       }
+       return 0;
+}
+
+/*
+ * pass3.c -- pass #3 of e2fsck: Check for directory connectivity
+ *
+ * Pass #3 assures that all directories are connected to the
+ * filesystem tree, using the following algorithm:
+ *
+ * First, the root directory is checked to make sure it exists; if
+ * not, e2fsck will offer to create a new one.  It is then marked as
+ * "done".
+ *
+ * Then, pass3 interates over all directory inodes; for each directory
+ * it attempts to trace up the filesystem tree, using dirinfo.parent
+ * until it reaches a directory which has been marked "done".  If it
+ * cannot do so, then the directory must be disconnected, and e2fsck
+ * will offer to reconnect it to /lost+found.  While it is chasing
+ * parent pointers up the filesystem tree, if pass3 sees a directory
+ * twice, then it has detected a filesystem loop, and it will again
+ * offer to reconnect the directory to /lost+found in to break the
+ * filesystem loop.
+ *
+ * Pass 3 also contains the subroutine, e2fsck_reconnect_file() to
+ * reconnect inodes to /lost+found; this subroutine is also used by
+ * pass 4.  e2fsck_reconnect_file() calls get_lost_and_found(), which
+ * is responsible for creating /lost+found if it does not exist.
+ *
+ * Pass 3 frees the following data structures:
+ *      - The dirinfo directory information cache.
+ */
+
+static void check_root(e2fsck_t ctx);
+static int check_directory(e2fsck_t ctx, struct dir_info *dir,
+                          struct problem_context *pctx);
+static void fix_dotdot(e2fsck_t ctx, struct dir_info *dir, ext2_ino_t parent);
+
+static ext2fs_inode_bitmap inode_loop_detect;
+static ext2fs_inode_bitmap inode_done_map;
+
+static void e2fsck_pass3(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       int             i;
+       struct problem_context  pctx;
+       struct dir_info *dir;
+       unsigned long maxdirs, count;
+
+       clear_problem_context(&pctx);
+
+       /* Pass 3 */
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               fix_problem(ctx, PR_3_PASS_HEADER, &pctx);
+
+       /*
+        * Allocate some bitmaps to do loop detection.
+        */
+       pctx.errcode = ext2fs_allocate_inode_bitmap(fs, _("inode done bitmap"),
+                                                   &inode_done_map);
+       if (pctx.errcode) {
+               pctx.num = 2;
+               fix_problem(ctx, PR_3_ALLOCATE_IBITMAP_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               goto abort_exit;
+       }
+       check_root(ctx);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               goto abort_exit;
+
+       ext2fs_mark_inode_bitmap(inode_done_map, EXT2_ROOT_INO);
+
+       maxdirs = e2fsck_get_num_dirinfo(ctx);
+       count = 1;
+
+       if (ctx->progress)
+               if ((ctx->progress)(ctx, 3, 0, maxdirs))
+                       goto abort_exit;
+
+       for (i=0; (dir = e2fsck_dir_info_iter(ctx, &i)) != 0;) {
+               if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                       goto abort_exit;
+               if (ctx->progress && (ctx->progress)(ctx, 3, count++, maxdirs))
+                       goto abort_exit;
+               if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, dir->ino))
+                       if (check_directory(ctx, dir, &pctx))
+                               goto abort_exit;
+       }
+
+       /*
+        * Force the creation of /lost+found if not present
+        */
+       if ((ctx->flags & E2F_OPT_READONLY) == 0)
+               e2fsck_get_lost_and_found(ctx, 1);
+
+       /*
+        * If there are any directories that need to be indexed or
+        * optimized, do it here.
+        */
+       e2fsck_rehash_directories(ctx);
+
+abort_exit:
+       e2fsck_free_dir_info(ctx);
+       ext2fs_free_inode_bitmap(inode_loop_detect);
+       inode_loop_detect = 0;
+       ext2fs_free_inode_bitmap(inode_done_map);
+       inode_done_map = 0;
+}
+
+/*
+ * This makes sure the root inode is present; if not, we ask if the
+ * user wants us to create it.  Not creating it is a fatal error.
+ */
+static void check_root(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       blk_t                   blk;
+       struct ext2_inode       inode;
+       char *                  block;
+       struct problem_context  pctx;
+
+       clear_problem_context(&pctx);
+
+       if (ext2fs_test_inode_bitmap(ctx->inode_used_map, EXT2_ROOT_INO)) {
+               /*
+                * If the root inode is not a directory, die here.  The
+                * user must have answered 'no' in pass1 when we
+                * offered to clear it.
+                */
+               if (!(ext2fs_test_inode_bitmap(ctx->inode_dir_map,
+                                              EXT2_ROOT_INO))) {
+                       fix_problem(ctx, PR_3_ROOT_NOT_DIR_ABORT, &pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+               }
+               return;
+       }
+
+       if (!fix_problem(ctx, PR_3_NO_ROOT_INODE, &pctx)) {
+               fix_problem(ctx, PR_3_NO_ROOT_INODE_ABORT, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+       e2fsck_read_bitmaps(ctx);
+
+       /*
+        * First, find a free block
+        */
+       pctx.errcode = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk);
+       if (pctx.errcode) {
+               pctx.str = "ext2fs_new_block";
+               fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+       ext2fs_mark_block_bitmap(ctx->block_found_map, blk);
+       ext2fs_mark_block_bitmap(fs->block_map, blk);
+       ext2fs_mark_bb_dirty(fs);
+
+       /*
+        * Now let's create the actual data block for the inode
+        */
+       pctx.errcode = ext2fs_new_dir_block(fs, EXT2_ROOT_INO, EXT2_ROOT_INO,
+                                           &block);
+       if (pctx.errcode) {
+               pctx.str = "ext2fs_new_dir_block";
+               fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+       pctx.errcode = ext2fs_write_dir_block(fs, blk, block);
+       if (pctx.errcode) {
+               pctx.str = "ext2fs_write_dir_block";
+               fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+       ext2fs_free_mem(&block);
+
+       /*
+        * Set up the inode structure
+        */
+       memset(&inode, 0, sizeof(inode));
+       inode.i_mode = 040755;
+       inode.i_size = fs->blocksize;
+       inode.i_atime = inode.i_ctime = inode.i_mtime = time(0);
+       inode.i_links_count = 2;
+       inode.i_blocks = fs->blocksize / 512;
+       inode.i_block[0] = blk;
+
+       /*
+        * Write out the inode.
+        */
+       pctx.errcode = ext2fs_write_new_inode(fs, EXT2_ROOT_INO, &inode);
+       if (pctx.errcode) {
+               pctx.str = "ext2fs_write_inode";
+               fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+       /*
+        * Miscellaneous bookkeeping...
+        */
+       e2fsck_add_dir_info(ctx, EXT2_ROOT_INO, EXT2_ROOT_INO);
+       ext2fs_icount_store(ctx->inode_count, EXT2_ROOT_INO, 2);
+       ext2fs_icount_store(ctx->inode_link_info, EXT2_ROOT_INO, 2);
+
+       ext2fs_mark_inode_bitmap(ctx->inode_used_map, EXT2_ROOT_INO);
+       ext2fs_mark_inode_bitmap(ctx->inode_dir_map, EXT2_ROOT_INO);
+       ext2fs_mark_inode_bitmap(fs->inode_map, EXT2_ROOT_INO);
+       ext2fs_mark_ib_dirty(fs);
+}
+
+/*
+ * This subroutine is responsible for making sure that a particular
+ * directory is connected to the root; if it isn't we trace it up as
+ * far as we can go, and then offer to connect the resulting parent to
+ * the lost+found.  We have to do loop detection; if we ever discover
+ * a loop, we treat that as a disconnected directory and offer to
+ * reparent it to lost+found.
+ *
+ * However, loop detection is expensive, because for very large
+ * filesystems, the inode_loop_detect bitmap is huge, and clearing it
+ * is non-trivial.  Loops in filesystems are also a rare error case,
+ * and we shouldn't optimize for error cases.  So we try two passes of
+ * the algorithm.  The first time, we ignore loop detection and merely
+ * increment a counter; if the counter exceeds some extreme threshold,
+ * then we try again with the loop detection bitmap enabled.
+ */
+static int check_directory(e2fsck_t ctx, struct dir_info *dir,
+                          struct problem_context *pctx)
+{
+       ext2_filsys     fs = ctx->fs;
+       struct dir_info *p = dir;
+       int             loop_pass = 0, parent_count = 0;
+
+       if (!p)
+               return 0;
+
+       while (1) {
+               /*
+                * Mark this inode as being "done"; by the time we
+                * return from this function, the inode we either be
+                * verified as being connected to the directory tree,
+                * or we will have offered to reconnect this to
+                * lost+found.
+                *
+                * If it was marked done already, then we've reached a
+                * parent we've already checked.
+                */
+               if (ext2fs_mark_inode_bitmap(inode_done_map, p->ino))
+                       break;
+
+               /*
+                * If this directory doesn't have a parent, or we've
+                * seen the parent once already, then offer to
+                * reparent it to lost+found
+                */
+               if (!p->parent ||
+                   (loop_pass &&
+                    (ext2fs_test_inode_bitmap(inode_loop_detect,
+                                             p->parent)))) {
+                       pctx->ino = p->ino;
+                       if (fix_problem(ctx, PR_3_UNCONNECTED_DIR, pctx)) {
+                               if (e2fsck_reconnect_file(ctx, pctx->ino))
+                                       ext2fs_unmark_valid(fs);
+                               else {
+                                       p = e2fsck_get_dir_info(ctx, pctx->ino);
+                                       p->parent = ctx->lost_and_found;
+                                       fix_dotdot(ctx, p, ctx->lost_and_found);
+                               }
+                       }
+                       break;
+               }
+               p = e2fsck_get_dir_info(ctx, p->parent);
+               if (!p) {
+                       fix_problem(ctx, PR_3_NO_DIRINFO, pctx);
+                       return 0;
+               }
+               if (loop_pass) {
+                       ext2fs_mark_inode_bitmap(inode_loop_detect,
+                                                p->ino);
+               } else if (parent_count++ > 2048) {
+                       /*
+                        * If we've run into a path depth that's
+                        * greater than 2048, try again with the inode
+                        * loop bitmap turned on and start from the
+                        * top.
+                        */
+                       loop_pass = 1;
+                       if (inode_loop_detect)
+                               ext2fs_clear_inode_bitmap(inode_loop_detect);
+                       else {
+                               pctx->errcode = ext2fs_allocate_inode_bitmap(fs, _("inode loop detection bitmap"), &inode_loop_detect);
+                               if (pctx->errcode) {
+                                       pctx->num = 1;
+                                       fix_problem(ctx,
+                                   PR_3_ALLOCATE_IBITMAP_ERROR, pctx);
+                                       ctx->flags |= E2F_FLAG_ABORT;
+                                       return -1;
+                               }
+                       }
+                       p = dir;
+               }
+       }
+
+       /*
+        * Make sure that .. and the parent directory are the same;
+        * offer to fix it if not.
+        */
+       if (dir->parent != dir->dotdot) {
+               pctx->ino = dir->ino;
+               pctx->ino2 = dir->dotdot;
+               pctx->dir = dir->parent;
+               if (fix_problem(ctx, PR_3_BAD_DOT_DOT, pctx))
+                       fix_dotdot(ctx, dir, dir->parent);
+       }
+       return 0;
+}
+
+/*
+ * This routine gets the lost_and_found inode, making it a directory
+ * if necessary
+ */
+ext2_ino_t e2fsck_get_lost_and_found(e2fsck_t ctx, int fix)
+{
+       ext2_filsys fs = ctx->fs;
+       ext2_ino_t                      ino;
+       blk_t                   blk;
+       errcode_t               retval;
+       struct ext2_inode       inode;
+       char *                  block;
+       static const char       name[] = "lost+found";
+       struct  problem_context pctx;
+       struct dir_info         *dirinfo;
+
+       if (ctx->lost_and_found)
+               return ctx->lost_and_found;
+
+       clear_problem_context(&pctx);
+
+       retval = ext2fs_lookup(fs, EXT2_ROOT_INO, name,
+                              sizeof(name)-1, 0, &ino);
+       if (retval && !fix)
+               return 0;
+       if (!retval) {
+               if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, ino)) {
+                       ctx->lost_and_found = ino;
+                       return ino;
+               }
+
+               /* Lost+found isn't a directory! */
+               if (!fix)
+                       return 0;
+               pctx.ino = ino;
+               if (!fix_problem(ctx, PR_3_LPF_NOTDIR, &pctx))
+                       return 0;
+
+               /* OK, unlink the old /lost+found file. */
+               pctx.errcode = ext2fs_unlink(fs, EXT2_ROOT_INO, name, ino, 0);
+               if (pctx.errcode) {
+                       pctx.str = "ext2fs_unlink";
+                       fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx);
+                       return 0;
+               }
+               dirinfo = e2fsck_get_dir_info(ctx, ino);
+               if (dirinfo)
+                       dirinfo->parent = 0;
+               e2fsck_adjust_inode_count(ctx, ino, -1);
+       } else if (retval != EXT2_ET_FILE_NOT_FOUND) {
+               pctx.errcode = retval;
+               fix_problem(ctx, PR_3_ERR_FIND_LPF, &pctx);
+       }
+       if (!fix_problem(ctx, PR_3_NO_LF_DIR, 0))
+               return 0;
+
+       /*
+        * Read the inode and block bitmaps in; we'll be messing with
+        * them.
+        */
+       e2fsck_read_bitmaps(ctx);
+
+       /*
+        * First, find a free block
+        */
+       retval = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk);
+       if (retval) {
+               pctx.errcode = retval;
+               fix_problem(ctx, PR_3_ERR_LPF_NEW_BLOCK, &pctx);
+               return 0;
+       }
+       ext2fs_mark_block_bitmap(ctx->block_found_map, blk);
+       ext2fs_block_alloc_stats(fs, blk, +1);
+
+       /*
+        * Next find a free inode.
+        */
+       retval = ext2fs_new_inode(fs, EXT2_ROOT_INO, 040700,
+                                 ctx->inode_used_map, &ino);
+       if (retval) {
+               pctx.errcode = retval;
+               fix_problem(ctx, PR_3_ERR_LPF_NEW_INODE, &pctx);
+               return 0;
+       }
+       ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
+       ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino);
+       ext2fs_inode_alloc_stats2(fs, ino, +1, 1);
+
+       /*
+        * Now let's create the actual data block for the inode
+        */
+       retval = ext2fs_new_dir_block(fs, ino, EXT2_ROOT_INO, &block);
+       if (retval) {
+               pctx.errcode = retval;
+               fix_problem(ctx, PR_3_ERR_LPF_NEW_DIR_BLOCK, &pctx);
+               return 0;
+       }
+
+       retval = ext2fs_write_dir_block(fs, blk, block);
+       ext2fs_free_mem(&block);
+       if (retval) {
+               pctx.errcode = retval;
+               fix_problem(ctx, PR_3_ERR_LPF_WRITE_BLOCK, &pctx);
+               return 0;
+       }
+
+       /*
+        * Set up the inode structure
+        */
+       memset(&inode, 0, sizeof(inode));
+       inode.i_mode = 040700;
+       inode.i_size = fs->blocksize;
+       inode.i_atime = inode.i_ctime = inode.i_mtime = time(0);
+       inode.i_links_count = 2;
+       inode.i_blocks = fs->blocksize / 512;
+       inode.i_block[0] = blk;
+
+       /*
+        * Next, write out the inode.
+        */
+       pctx.errcode = ext2fs_write_new_inode(fs, ino, &inode);
+       if (pctx.errcode) {
+               pctx.str = "ext2fs_write_inode";
+               fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx);
+               return 0;
+       }
+       /*
+        * Finally, create the directory link
+        */
+       pctx.errcode = ext2fs_link(fs, EXT2_ROOT_INO, name, ino, EXT2_FT_DIR);
+       if (pctx.errcode) {
+               pctx.str = "ext2fs_link";
+               fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx);
+               return 0;
+       }
+
+       /*
+        * Miscellaneous bookkeeping that needs to be kept straight.
+        */
+       e2fsck_add_dir_info(ctx, ino, EXT2_ROOT_INO);
+       e2fsck_adjust_inode_count(ctx, EXT2_ROOT_INO, 1);
+       ext2fs_icount_store(ctx->inode_count, ino, 2);
+       ext2fs_icount_store(ctx->inode_link_info, ino, 2);
+       ctx->lost_and_found = ino;
+       return ino;
+}
+
+/*
+ * This routine will connect a file to lost+found
+ */
+int e2fsck_reconnect_file(e2fsck_t ctx, ext2_ino_t ino)
+{
+       ext2_filsys fs = ctx->fs;
+       errcode_t       retval;
+       char            name[80];
+       struct problem_context  pctx;
+       struct ext2_inode       inode;
+       int             file_type = 0;
+
+       clear_problem_context(&pctx);
+       pctx.ino = ino;
+
+       if (!ctx->bad_lost_and_found && !ctx->lost_and_found) {
+               if (e2fsck_get_lost_and_found(ctx, 1) == 0)
+                       ctx->bad_lost_and_found++;
+       }
+       if (ctx->bad_lost_and_found) {
+               fix_problem(ctx, PR_3_NO_LPF, &pctx);
+               return 1;
+       }
+
+       sprintf(name, "#%u", ino);
+       if (ext2fs_read_inode(fs, ino, &inode) == 0)
+               file_type = ext2_file_type(inode.i_mode);
+       retval = ext2fs_link(fs, ctx->lost_and_found, name, ino, file_type);
+       if (retval == EXT2_ET_DIR_NO_SPACE) {
+               if (!fix_problem(ctx, PR_3_EXPAND_LF_DIR, &pctx))
+                       return 1;
+               retval = e2fsck_expand_directory(ctx, ctx->lost_and_found,
+                                                1, 0);
+               if (retval) {
+                       pctx.errcode = retval;
+                       fix_problem(ctx, PR_3_CANT_EXPAND_LPF, &pctx);
+                       return 1;
+               }
+               retval = ext2fs_link(fs, ctx->lost_and_found, name,
+                                    ino, file_type);
+       }
+       if (retval) {
+               pctx.errcode = retval;
+               fix_problem(ctx, PR_3_CANT_RECONNECT, &pctx);
+               return 1;
+       }
+       e2fsck_adjust_inode_count(ctx, ino, 1);
+
+       return 0;
+}
+
+/*
+ * Utility routine to adjust the inode counts on an inode.
+ */
+errcode_t e2fsck_adjust_inode_count(e2fsck_t ctx, ext2_ino_t ino, int adj)
+{
+       ext2_filsys fs = ctx->fs;
+       errcode_t               retval;
+       struct ext2_inode       inode;
+
+       if (!ino)
+               return 0;
+
+       retval = ext2fs_read_inode(fs, ino, &inode);
+       if (retval)
+               return retval;
+
+       if (adj == 1) {
+               ext2fs_icount_increment(ctx->inode_count, ino, 0);
+               if (inode.i_links_count == (__u16) ~0)
+                       return 0;
+               ext2fs_icount_increment(ctx->inode_link_info, ino, 0);
+               inode.i_links_count++;
+       } else if (adj == -1) {
+               ext2fs_icount_decrement(ctx->inode_count, ino, 0);
+               if (inode.i_links_count == 0)
+                       return 0;
+               ext2fs_icount_decrement(ctx->inode_link_info, ino, 0);
+               inode.i_links_count--;
+       }
+
+       retval = ext2fs_write_inode(fs, ino, &inode);
+       if (retval)
+               return retval;
+
+       return 0;
+}
+
+/*
+ * Fix parent --- this routine fixes up the parent of a directory.
+ */
+struct fix_dotdot_struct {
+       ext2_filsys     fs;
+       ext2_ino_t      parent;
+       int             done;
+       e2fsck_t        ctx;
+};
+
+static int fix_dotdot_proc(struct ext2_dir_entry *dirent,
+                          int  offset FSCK_ATTR((unused)),
+                          int  blocksize FSCK_ATTR((unused)),
+                          char *buf FSCK_ATTR((unused)),
+                          void *priv_data)
+{
+       struct fix_dotdot_struct *fp = (struct fix_dotdot_struct *) priv_data;
+       errcode_t       retval;
+       struct problem_context pctx;
+
+       if ((dirent->name_len & 0xFF) != 2)
+               return 0;
+       if (strncmp(dirent->name, "..", 2))
+               return 0;
+
+       clear_problem_context(&pctx);
+
+       retval = e2fsck_adjust_inode_count(fp->ctx, dirent->inode, -1);
+       if (retval) {
+               pctx.errcode = retval;
+               fix_problem(fp->ctx, PR_3_ADJUST_INODE, &pctx);
+       }
+       retval = e2fsck_adjust_inode_count(fp->ctx, fp->parent, 1);
+       if (retval) {
+               pctx.errcode = retval;
+               fix_problem(fp->ctx, PR_3_ADJUST_INODE, &pctx);
+       }
+       dirent->inode = fp->parent;
+
+       fp->done++;
+       return DIRENT_ABORT | DIRENT_CHANGED;
+}
+
+static void fix_dotdot(e2fsck_t ctx, struct dir_info *dir, ext2_ino_t parent)
+{
+       ext2_filsys fs = ctx->fs;
+       errcode_t       retval;
+       struct fix_dotdot_struct fp;
+       struct problem_context pctx;
+
+       fp.fs = fs;
+       fp.parent = parent;
+       fp.done = 0;
+       fp.ctx = ctx;
+
+       retval = ext2fs_dir_iterate(fs, dir->ino, DIRENT_FLAG_INCLUDE_EMPTY,
+                                   0, fix_dotdot_proc, &fp);
+       if (retval || !fp.done) {
+               clear_problem_context(&pctx);
+               pctx.ino = dir->ino;
+               pctx.errcode = retval;
+               fix_problem(ctx, retval ? PR_3_FIX_PARENT_ERR :
+                           PR_3_FIX_PARENT_NOFIND, &pctx);
+               ext2fs_unmark_valid(fs);
+       }
+       dir->dotdot = parent;
+}
+
+/*
+ * These routines are responsible for expanding a /lost+found if it is
+ * too small.
+ */
+
+struct expand_dir_struct {
+       int                     num;
+       int                     guaranteed_size;
+       int                     newblocks;
+       int                     last_block;
+       errcode_t               err;
+       e2fsck_t                ctx;
+};
+
+static int expand_dir_proc(ext2_filsys fs,
+                          blk_t        *blocknr,
+                          e2_blkcnt_t  blockcnt,
+                          blk_t ref_block FSCK_ATTR((unused)),
+                          int ref_offset FSCK_ATTR((unused)),
+                          void *priv_data)
+{
+       struct expand_dir_struct *es = (struct expand_dir_struct *) priv_data;
+       blk_t   new_blk;
+       static blk_t    last_blk = 0;
+       char            *block;
+       errcode_t       retval;
+       e2fsck_t        ctx;
+
+       ctx = es->ctx;
+
+       if (es->guaranteed_size && blockcnt >= es->guaranteed_size)
+               return BLOCK_ABORT;
+
+       if (blockcnt > 0)
+               es->last_block = blockcnt;
+       if (*blocknr) {
+               last_blk = *blocknr;
+               return 0;
+       }
+       retval = ext2fs_new_block(fs, last_blk, ctx->block_found_map,
+                                 &new_blk);
+       if (retval) {
+               es->err = retval;
+               return BLOCK_ABORT;
+       }
+       if (blockcnt > 0) {
+               retval = ext2fs_new_dir_block(fs, 0, 0, &block);
+               if (retval) {
+                       es->err = retval;
+                       return BLOCK_ABORT;
+               }
+               es->num--;
+               retval = ext2fs_write_dir_block(fs, new_blk, block);
+       } else {
+               retval = ext2fs_get_mem(fs->blocksize, &block);
+               if (retval) {
+                       es->err = retval;
+                       return BLOCK_ABORT;
+               }
+               memset(block, 0, fs->blocksize);
+               retval = io_channel_write_blk(fs->io, new_blk, 1, block);
+       }
+       if (retval) {
+               es->err = retval;
+               return BLOCK_ABORT;
+       }
+       ext2fs_free_mem(&block);
+       *blocknr = new_blk;
+       ext2fs_mark_block_bitmap(ctx->block_found_map, new_blk);
+       ext2fs_block_alloc_stats(fs, new_blk, +1);
+       es->newblocks++;
+
+       if (es->num == 0)
+               return (BLOCK_CHANGED | BLOCK_ABORT);
+       else
+               return BLOCK_CHANGED;
+}
+
+errcode_t e2fsck_expand_directory(e2fsck_t ctx, ext2_ino_t dir,
+                                 int num, int guaranteed_size)
+{
+       ext2_filsys fs = ctx->fs;
+       errcode_t       retval;
+       struct expand_dir_struct es;
+       struct ext2_inode       inode;
+
+       if (!(fs->flags & EXT2_FLAG_RW))
+               return EXT2_ET_RO_FILSYS;
+
+       /*
+        * Read the inode and block bitmaps in; we'll be messing with
+        * them.
+        */
+       e2fsck_read_bitmaps(ctx);
+
+       retval = ext2fs_check_directory(fs, dir);
+       if (retval)
+               return retval;
+
+       es.num = num;
+       es.guaranteed_size = guaranteed_size;
+       es.last_block = 0;
+       es.err = 0;
+       es.newblocks = 0;
+       es.ctx = ctx;
+
+       retval = ext2fs_block_iterate2(fs, dir, BLOCK_FLAG_APPEND,
+                                      0, expand_dir_proc, &es);
+
+       if (es.err)
+               return es.err;
+
+       /*
+        * Update the size and block count fields in the inode.
+        */
+       retval = ext2fs_read_inode(fs, dir, &inode);
+       if (retval)
+               return retval;
+
+       inode.i_size = (es.last_block + 1) * fs->blocksize;
+       inode.i_blocks += (fs->blocksize / 512) * es.newblocks;
+
+       e2fsck_write_inode(ctx, dir, &inode, "expand_directory");
+
+       return 0;
+}
+
+/*
+ * pass4.c -- pass #4 of e2fsck: Check reference counts
+ *
+ * Pass 4 frees the following data structures:
+ *      - A bitmap of which inodes are imagic inodes.   (inode_imagic_map)
+ */
+
+/*
+ * This routine is called when an inode is not connected to the
+ * directory tree.
+ *
+ * This subroutine returns 1 then the caller shouldn't bother with the
+ * rest of the pass 4 tests.
+ */
+static int disconnect_inode(e2fsck_t ctx, ext2_ino_t i)
+{
+       ext2_filsys fs = ctx->fs;
+       struct ext2_inode       inode;
+       struct problem_context  pctx;
+
+       e2fsck_read_inode(ctx, i, &inode, "pass4: disconnect_inode");
+       clear_problem_context(&pctx);
+       pctx.ino = i;
+       pctx.inode = &inode;
+
+       /*
+        * Offer to delete any zero-length files that does not have
+        * blocks.  If there is an EA block, it might have useful
+        * information, so we won't prompt to delete it, but let it be
+        * reconnected to lost+found.
+        */
+       if (!inode.i_blocks && (LINUX_S_ISREG(inode.i_mode) ||
+                               LINUX_S_ISDIR(inode.i_mode))) {
+               if (fix_problem(ctx, PR_4_ZERO_LEN_INODE, &pctx)) {
+                       ext2fs_icount_store(ctx->inode_link_info, i, 0);
+                       inode.i_links_count = 0;
+                       inode.i_dtime = time(0);
+                       e2fsck_write_inode(ctx, i, &inode,
+                                          "disconnect_inode");
+                       /*
+                        * Fix up the bitmaps...
+                        */
+                       e2fsck_read_bitmaps(ctx);
+                       ext2fs_unmark_inode_bitmap(ctx->inode_used_map, i);
+                       ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, i);
+                       ext2fs_inode_alloc_stats2(fs, i, -1,
+                                                 LINUX_S_ISDIR(inode.i_mode));
+                       return 0;
+               }
+       }
+
+       /*
+        * Prompt to reconnect.
+        */
+       if (fix_problem(ctx, PR_4_UNATTACHED_INODE, &pctx)) {
+               if (e2fsck_reconnect_file(ctx, i))
+                       ext2fs_unmark_valid(fs);
+       } else {
+               /*
+                * If we don't attach the inode, then skip the
+                * i_links_test since there's no point in trying to
+                * force i_links_count to zero.
+                */
+               ext2fs_unmark_valid(fs);
+               return 1;
+       }
+       return 0;
+}
+
+
+static void e2fsck_pass4(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       ext2_ino_t      i;
+       struct ext2_inode       inode;
+       struct problem_context  pctx;
+       __u16   link_count, link_counted;
+       char    *buf = 0;
+       int     group, maxgroup;
+
+       /* Pass 4 */
+
+       clear_problem_context(&pctx);
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               fix_problem(ctx, PR_4_PASS_HEADER, &pctx);
+
+       group = 0;
+       maxgroup = fs->group_desc_count;
+       if (ctx->progress)
+               if ((ctx->progress)(ctx, 4, 0, maxgroup))
+                       return;
+
+       for (i=1; i <= fs->super->s_inodes_count; i++) {
+               if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                       return;
+               if ((i % fs->super->s_inodes_per_group) == 0) {
+                       group++;
+                       if (ctx->progress)
+                               if ((ctx->progress)(ctx, 4, group, maxgroup))
+                                       return;
+               }
+               if (i == EXT2_BAD_INO ||
+                   (i > EXT2_ROOT_INO && i < EXT2_FIRST_INODE(fs->super)))
+                       continue;
+               if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, i)) ||
+                   (ctx->inode_imagic_map &&
+                    ext2fs_test_inode_bitmap(ctx->inode_imagic_map, i)))
+                       continue;
+               ext2fs_icount_fetch(ctx->inode_link_info, i, &link_count);
+               ext2fs_icount_fetch(ctx->inode_count, i, &link_counted);
+               if (link_counted == 0) {
+                       if (!buf)
+                               buf = e2fsck_allocate_memory(ctx,
+                                    fs->blocksize, "bad_inode buffer");
+                       if (e2fsck_process_bad_inode(ctx, 0, i, buf))
+                               continue;
+                       if (disconnect_inode(ctx, i))
+                               continue;
+                       ext2fs_icount_fetch(ctx->inode_link_info, i,
+                                           &link_count);
+                       ext2fs_icount_fetch(ctx->inode_count, i,
+                                           &link_counted);
+               }
+               if (link_counted != link_count) {
+                       e2fsck_read_inode(ctx, i, &inode, "pass4");
+                       pctx.ino = i;
+                       pctx.inode = &inode;
+                       if (link_count != inode.i_links_count) {
+                               pctx.num = link_count;
+                               fix_problem(ctx,
+                                           PR_4_INCONSISTENT_COUNT, &pctx);
+                       }
+                       pctx.num = link_counted;
+                       if (fix_problem(ctx, PR_4_BAD_REF_COUNT, &pctx)) {
+                               inode.i_links_count = link_counted;
+                               e2fsck_write_inode(ctx, i, &inode, "pass4");
+                       }
+               }
+       }
+       ext2fs_free_icount(ctx->inode_link_info); ctx->inode_link_info = 0;
+       ext2fs_free_icount(ctx->inode_count); ctx->inode_count = 0;
+       ext2fs_free_inode_bitmap(ctx->inode_imagic_map);
+       ctx->inode_imagic_map = 0;
+       ext2fs_free_mem(&buf);
+}
+
+/*
+ * pass5.c --- check block and inode bitmaps against on-disk bitmaps
+ */
+
+#define NO_BLK ((blk_t) -1)
+
+static void print_bitmap_problem(e2fsck_t ctx, int problem,
+                           struct problem_context *pctx)
+{
+       switch (problem) {
+       case PR_5_BLOCK_UNUSED:
+               if (pctx->blk == pctx->blk2)
+                       pctx->blk2 = 0;
+               else
+                       problem = PR_5_BLOCK_RANGE_UNUSED;
+               break;
+       case PR_5_BLOCK_USED:
+               if (pctx->blk == pctx->blk2)
+                       pctx->blk2 = 0;
+               else
+                       problem = PR_5_BLOCK_RANGE_USED;
+               break;
+       case PR_5_INODE_UNUSED:
+               if (pctx->ino == pctx->ino2)
+                       pctx->ino2 = 0;
+               else
+                       problem = PR_5_INODE_RANGE_UNUSED;
+               break;
+       case PR_5_INODE_USED:
+               if (pctx->ino == pctx->ino2)
+                       pctx->ino2 = 0;
+               else
+                       problem = PR_5_INODE_RANGE_USED;
+               break;
+       }
+       fix_problem(ctx, problem, pctx);
+       pctx->blk = pctx->blk2 = NO_BLK;
+       pctx->ino = pctx->ino2 = 0;
+}
+
+static void check_block_bitmaps(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       blk_t   i;
+       int     *free_array;
+       int     group = 0;
+       unsigned int    blocks = 0;
+       unsigned int    free_blocks = 0;
+       int     group_free = 0;
+       int     actual, bitmap;
+       struct problem_context  pctx;
+       int     problem, save_problem, fixit, had_problem;
+       errcode_t       retval;
+
+       clear_problem_context(&pctx);
+       free_array = (int *) e2fsck_allocate_memory(ctx,
+           fs->group_desc_count * sizeof(int), "free block count array");
+
+       if ((fs->super->s_first_data_block <
+            ext2fs_get_block_bitmap_start(ctx->block_found_map)) ||
+           (fs->super->s_blocks_count-1 >
+            ext2fs_get_block_bitmap_end(ctx->block_found_map))) {
+               pctx.num = 1;
+               pctx.blk = fs->super->s_first_data_block;
+               pctx.blk2 = fs->super->s_blocks_count -1;
+               pctx.ino = ext2fs_get_block_bitmap_start(ctx->block_found_map);
+               pctx.ino2 = ext2fs_get_block_bitmap_end(ctx->block_found_map);
+               fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx);
+
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+
+       if ((fs->super->s_first_data_block <
+            ext2fs_get_block_bitmap_start(fs->block_map)) ||
+           (fs->super->s_blocks_count-1 >
+            ext2fs_get_block_bitmap_end(fs->block_map))) {
+               pctx.num = 2;
+               pctx.blk = fs->super->s_first_data_block;
+               pctx.blk2 = fs->super->s_blocks_count -1;
+               pctx.ino = ext2fs_get_block_bitmap_start(fs->block_map);
+               pctx.ino2 = ext2fs_get_block_bitmap_end(fs->block_map);
+               fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx);
+
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+
+redo_counts:
+       had_problem = 0;
+       save_problem = 0;
+       pctx.blk = pctx.blk2 = NO_BLK;
+       for (i = fs->super->s_first_data_block;
+            i < fs->super->s_blocks_count;
+            i++) {
+               actual = ext2fs_fast_test_block_bitmap(ctx->block_found_map, i);
+               bitmap = ext2fs_fast_test_block_bitmap(fs->block_map, i);
+
+               if (actual == bitmap)
+                       goto do_counts;
+
+               if (!actual && bitmap) {
+                       /*
+                        * Block not used, but marked in use in the bitmap.
+                        */
+                       problem = PR_5_BLOCK_UNUSED;
+               } else {
+                       /*
+                        * Block used, but not marked in use in the bitmap.
+                        */
+                       problem = PR_5_BLOCK_USED;
+               }
+               if (pctx.blk == NO_BLK) {
+                       pctx.blk = pctx.blk2 = i;
+                       save_problem = problem;
+               } else {
+                       if ((problem == save_problem) &&
+                           (pctx.blk2 == i-1))
+                               pctx.blk2++;
+                       else {
+                               print_bitmap_problem(ctx, save_problem, &pctx);
+                               pctx.blk = pctx.blk2 = i;
+                               save_problem = problem;
+                       }
+               }
+               ctx->flags |= E2F_FLAG_PROG_SUPPRESS;
+               had_problem++;
+
+       do_counts:
+               if (!bitmap) {
+                       group_free++;
+                       free_blocks++;
+               }
+               blocks ++;
+               if ((blocks == fs->super->s_blocks_per_group) ||
+                   (i == fs->super->s_blocks_count-1)) {
+                       free_array[group] = group_free;
+                       group ++;
+                       blocks = 0;
+                       group_free = 0;
+                       if (ctx->progress)
+                               if ((ctx->progress)(ctx, 5, group,
+                                                   fs->group_desc_count*2))
+                                       return;
+               }
+       }
+       if (pctx.blk != NO_BLK)
+               print_bitmap_problem(ctx, save_problem, &pctx);
+       if (had_problem)
+               fixit = end_problem_latch(ctx, PR_LATCH_BBITMAP);
+       else
+               fixit = -1;
+       ctx->flags &= ~E2F_FLAG_PROG_SUPPRESS;
+
+       if (fixit == 1) {
+               ext2fs_free_block_bitmap(fs->block_map);
+               retval = ext2fs_copy_bitmap(ctx->block_found_map,
+                                                 &fs->block_map);
+               if (retval) {
+                       clear_problem_context(&pctx);
+                       fix_problem(ctx, PR_5_COPY_BBITMAP_ERROR, &pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+               ext2fs_set_bitmap_padding(fs->block_map);
+               ext2fs_mark_bb_dirty(fs);
+
+               /* Redo the counts */
+               blocks = 0; free_blocks = 0; group_free = 0; group = 0;
+               memset(free_array, 0, fs->group_desc_count * sizeof(int));
+               goto redo_counts;
+       } else if (fixit == 0)
+               ext2fs_unmark_valid(fs);
+
+       for (i = 0; i < fs->group_desc_count; i++) {
+               if (free_array[i] != fs->group_desc[i].bg_free_blocks_count) {
+                       pctx.group = i;
+                       pctx.blk = fs->group_desc[i].bg_free_blocks_count;
+                       pctx.blk2 = free_array[i];
+
+                       if (fix_problem(ctx, PR_5_FREE_BLOCK_COUNT_GROUP,
+                                       &pctx)) {
+                               fs->group_desc[i].bg_free_blocks_count =
+                                       free_array[i];
+                               ext2fs_mark_super_dirty(fs);
+                       } else
+                               ext2fs_unmark_valid(fs);
+               }
+       }
+       if (free_blocks != fs->super->s_free_blocks_count) {
+               pctx.group = 0;
+               pctx.blk = fs->super->s_free_blocks_count;
+               pctx.blk2 = free_blocks;
+
+               if (fix_problem(ctx, PR_5_FREE_BLOCK_COUNT, &pctx)) {
+                       fs->super->s_free_blocks_count = free_blocks;
+                       ext2fs_mark_super_dirty(fs);
+               } else
+                       ext2fs_unmark_valid(fs);
+       }
+       ext2fs_free_mem(&free_array);
+}
+
+static void check_inode_bitmaps(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       ext2_ino_t      i;
+       unsigned int    free_inodes = 0;
+       int             group_free = 0;
+       int             dirs_count = 0;
+       int             group = 0;
+       unsigned int    inodes = 0;
+       int             *free_array;
+       int             *dir_array;
+       int             actual, bitmap;
+       errcode_t       retval;
+       struct problem_context  pctx;
+       int             problem, save_problem, fixit, had_problem;
+
+       clear_problem_context(&pctx);
+       free_array = (int *) e2fsck_allocate_memory(ctx,
+           fs->group_desc_count * sizeof(int), "free inode count array");
+
+       dir_array = (int *) e2fsck_allocate_memory(ctx,
+          fs->group_desc_count * sizeof(int), "directory count array");
+
+       if ((1 < ext2fs_get_inode_bitmap_start(ctx->inode_used_map)) ||
+           (fs->super->s_inodes_count >
+            ext2fs_get_inode_bitmap_end(ctx->inode_used_map))) {
+               pctx.num = 3;
+               pctx.blk = 1;
+               pctx.blk2 = fs->super->s_inodes_count;
+               pctx.ino = ext2fs_get_inode_bitmap_start(ctx->inode_used_map);
+               pctx.ino2 = ext2fs_get_inode_bitmap_end(ctx->inode_used_map);
+               fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx);
+
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+       if ((1 < ext2fs_get_inode_bitmap_start(fs->inode_map)) ||
+           (fs->super->s_inodes_count >
+            ext2fs_get_inode_bitmap_end(fs->inode_map))) {
+               pctx.num = 4;
+               pctx.blk = 1;
+               pctx.blk2 = fs->super->s_inodes_count;
+               pctx.ino = ext2fs_get_inode_bitmap_start(fs->inode_map);
+               pctx.ino2 = ext2fs_get_inode_bitmap_end(fs->inode_map);
+               fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx);
+
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+
+redo_counts:
+       had_problem = 0;
+       save_problem = 0;
+       pctx.ino = pctx.ino2 = 0;
+       for (i = 1; i <= fs->super->s_inodes_count; i++) {
+               actual = ext2fs_fast_test_inode_bitmap(ctx->inode_used_map, i);
+               bitmap = ext2fs_fast_test_inode_bitmap(fs->inode_map, i);
+
+               if (actual == bitmap)
+                       goto do_counts;
+
+               if (!actual && bitmap) {
+                       /*
+                        * Inode wasn't used, but marked in bitmap
+                        */
+                       problem = PR_5_INODE_UNUSED;
+               } else /* if (actual && !bitmap) */ {
+                       /*
+                        * Inode used, but not in bitmap
+                        */
+                       problem = PR_5_INODE_USED;
+               }
+               if (pctx.ino == 0) {
+                       pctx.ino = pctx.ino2 = i;
+                       save_problem = problem;
+               } else {
+                       if ((problem == save_problem) &&
+                           (pctx.ino2 == i-1))
+                               pctx.ino2++;
+                       else {
+                               print_bitmap_problem(ctx, save_problem, &pctx);
+                               pctx.ino = pctx.ino2 = i;
+                               save_problem = problem;
+                       }
+               }
+               ctx->flags |= E2F_FLAG_PROG_SUPPRESS;
+               had_problem++;
+
+do_counts:
+               if (!bitmap) {
+                       group_free++;
+                       free_inodes++;
+               } else {
+                       if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, i))
+                               dirs_count++;
+               }
+               inodes++;
+               if ((inodes == fs->super->s_inodes_per_group) ||
+                   (i == fs->super->s_inodes_count)) {
+                       free_array[group] = group_free;
+                       dir_array[group] = dirs_count;
+                       group ++;
+                       inodes = 0;
+                       group_free = 0;
+                       dirs_count = 0;
+                       if (ctx->progress)
+                               if ((ctx->progress)(ctx, 5,
+                                           group + fs->group_desc_count,
+                                           fs->group_desc_count*2))
+                                       return;
+               }
+       }
+       if (pctx.ino)
+               print_bitmap_problem(ctx, save_problem, &pctx);
+
+       if (had_problem)
+               fixit = end_problem_latch(ctx, PR_LATCH_IBITMAP);
+       else
+               fixit = -1;
+       ctx->flags &= ~E2F_FLAG_PROG_SUPPRESS;
+
+       if (fixit == 1) {
+               ext2fs_free_inode_bitmap(fs->inode_map);
+               retval = ext2fs_copy_bitmap(ctx->inode_used_map,
+                                                 &fs->inode_map);
+               if (retval) {
+                       clear_problem_context(&pctx);
+                       fix_problem(ctx, PR_5_COPY_IBITMAP_ERROR, &pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+               ext2fs_set_bitmap_padding(fs->inode_map);
+               ext2fs_mark_ib_dirty(fs);
+
+               /* redo counts */
+               inodes = 0; free_inodes = 0; group_free = 0;
+               dirs_count = 0; group = 0;
+               memset(free_array, 0, fs->group_desc_count * sizeof(int));
+               memset(dir_array, 0, fs->group_desc_count * sizeof(int));
+               goto redo_counts;
+       } else if (fixit == 0)
+               ext2fs_unmark_valid(fs);
+
+       for (i = 0; i < fs->group_desc_count; i++) {
+               if (free_array[i] != fs->group_desc[i].bg_free_inodes_count) {
+                       pctx.group = i;
+                       pctx.ino = fs->group_desc[i].bg_free_inodes_count;
+                       pctx.ino2 = free_array[i];
+                       if (fix_problem(ctx, PR_5_FREE_INODE_COUNT_GROUP,
+                                       &pctx)) {
+                               fs->group_desc[i].bg_free_inodes_count =
+                                       free_array[i];
+                               ext2fs_mark_super_dirty(fs);
+                       } else
+                               ext2fs_unmark_valid(fs);
+               }
+               if (dir_array[i] != fs->group_desc[i].bg_used_dirs_count) {
+                       pctx.group = i;
+                       pctx.ino = fs->group_desc[i].bg_used_dirs_count;
+                       pctx.ino2 = dir_array[i];
+
+                       if (fix_problem(ctx, PR_5_FREE_DIR_COUNT_GROUP,
+                                       &pctx)) {
+                               fs->group_desc[i].bg_used_dirs_count =
+                                       dir_array[i];
+                               ext2fs_mark_super_dirty(fs);
+                       } else
+                               ext2fs_unmark_valid(fs);
+               }
+       }
+       if (free_inodes != fs->super->s_free_inodes_count) {
+               pctx.group = -1;
+               pctx.ino = fs->super->s_free_inodes_count;
+               pctx.ino2 = free_inodes;
+
+               if (fix_problem(ctx, PR_5_FREE_INODE_COUNT, &pctx)) {
+                       fs->super->s_free_inodes_count = free_inodes;
+                       ext2fs_mark_super_dirty(fs);
+               } else
+                       ext2fs_unmark_valid(fs);
+       }
+       ext2fs_free_mem(&free_array);
+       ext2fs_free_mem(&dir_array);
+}
+
+static void check_inode_end(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       ext2_ino_t      end, save_inodes_count, i;
+       struct problem_context  pctx;
+
+       clear_problem_context(&pctx);
+
+       end = EXT2_INODES_PER_GROUP(fs->super) * fs->group_desc_count;
+       pctx.errcode = ext2fs_fudge_inode_bitmap_end(fs->inode_map, end,
+                                                    &save_inodes_count);
+       if (pctx.errcode) {
+               pctx.num = 1;
+               fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+       if (save_inodes_count == end)
+               return;
+
+       for (i = save_inodes_count + 1; i <= end; i++) {
+               if (!ext2fs_test_inode_bitmap(fs->inode_map, i)) {
+                       if (fix_problem(ctx, PR_5_INODE_BMAP_PADDING, &pctx)) {
+                               for (i = save_inodes_count + 1; i <= end; i++)
+                                       ext2fs_mark_inode_bitmap(fs->inode_map,
+                                                                i);
+                               ext2fs_mark_ib_dirty(fs);
+                       } else
+                               ext2fs_unmark_valid(fs);
+                       break;
+               }
+       }
+
+       pctx.errcode = ext2fs_fudge_inode_bitmap_end(fs->inode_map,
+                                                    save_inodes_count, 0);
+       if (pctx.errcode) {
+               pctx.num = 2;
+               fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+}
+
+static void check_block_end(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       blk_t   end, save_blocks_count, i;
+       struct problem_context  pctx;
+
+       clear_problem_context(&pctx);
+
+       end = fs->block_map->start +
+               (EXT2_BLOCKS_PER_GROUP(fs->super) * fs->group_desc_count) - 1;
+       pctx.errcode = ext2fs_fudge_block_bitmap_end(fs->block_map, end,
+                                                    &save_blocks_count);
+       if (pctx.errcode) {
+               pctx.num = 3;
+               fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+       if (save_blocks_count == end)
+               return;
+
+       for (i = save_blocks_count + 1; i <= end; i++) {
+               if (!ext2fs_test_block_bitmap(fs->block_map, i)) {
+                       if (fix_problem(ctx, PR_5_BLOCK_BMAP_PADDING, &pctx)) {
+                               for (i = save_blocks_count + 1; i <= end; i++)
+                                       ext2fs_mark_block_bitmap(fs->block_map,
+                                                                i);
+                               ext2fs_mark_bb_dirty(fs);
+                       } else
+                               ext2fs_unmark_valid(fs);
+                       break;
+               }
+       }
+
+       pctx.errcode = ext2fs_fudge_block_bitmap_end(fs->block_map,
+                                                    save_blocks_count, 0);
+       if (pctx.errcode) {
+               pctx.num = 4;
+               fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT; /* fatal */
+               return;
+       }
+}
+
+static void e2fsck_pass5(e2fsck_t ctx)
+{
+       struct problem_context  pctx;
+
+       /* Pass 5 */
+
+       clear_problem_context(&pctx);
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               fix_problem(ctx, PR_5_PASS_HEADER, &pctx);
+
+       if (ctx->progress)
+               if ((ctx->progress)(ctx, 5, 0, ctx->fs->group_desc_count*2))
+                       return;
+
+       e2fsck_read_bitmaps(ctx);
+
+       check_block_bitmaps(ctx);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               return;
+       check_inode_bitmaps(ctx);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               return;
+       check_inode_end(ctx);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               return;
+       check_block_end(ctx);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               return;
+
+       ext2fs_free_inode_bitmap(ctx->inode_used_map);
+       ctx->inode_used_map = 0;
+       ext2fs_free_inode_bitmap(ctx->inode_dir_map);
+       ctx->inode_dir_map = 0;
+       ext2fs_free_block_bitmap(ctx->block_found_map);
+       ctx->block_found_map = 0;
+}
+
+/*
+ * problem.c --- report filesystem problems to the user
+ */
+
+#define PR_PREEN_OK     0x000001 /* Don't need to do preenhalt */
+#define PR_NO_OK        0x000002 /* If user answers no, don't make fs invalid */
+#define PR_NO_DEFAULT   0x000004 /* Default to no */
+#define PR_MSG_ONLY     0x000008 /* Print message only */
+
+/* Bit positions 0x000ff0 are reserved for the PR_LATCH flags */
+
+#define PR_FATAL        0x001000 /* Fatal error */
+#define PR_AFTER_CODE   0x002000 /* After asking the first question, */
+                                /* ask another */
+#define PR_PREEN_NOMSG  0x004000 /* Don't print a message if we're preening */
+#define PR_NOCOLLATE    0x008000 /* Don't collate answers for this latch */
+#define PR_NO_NOMSG     0x010000 /* Don't print a message if e2fsck -n */
+#define PR_PREEN_NO     0x020000 /* Use No as an answer if preening */
+#define PR_PREEN_NOHDR  0x040000 /* Don't print the preen header */
+
+
+#define PROMPT_NONE     0
+#define PROMPT_FIX      1
+#define PROMPT_CLEAR    2
+#define PROMPT_RELOCATE 3
+#define PROMPT_ALLOCATE 4
+#define PROMPT_EXPAND   5
+#define PROMPT_CONNECT  6
+#define PROMPT_CREATE   7
+#define PROMPT_SALVAGE  8
+#define PROMPT_TRUNCATE 9
+#define PROMPT_CLEAR_INODE 10
+#define PROMPT_ABORT    11
+#define PROMPT_SPLIT    12
+#define PROMPT_CONTINUE 13
+#define PROMPT_CLONE    14
+#define PROMPT_DELETE   15
+#define PROMPT_SUPPRESS 16
+#define PROMPT_UNLINK   17
+#define PROMPT_CLEAR_HTREE 18
+#define PROMPT_RECREATE 19
+#define PROMPT_NULL     20
+
+struct e2fsck_problem {
+       problem_t       e2p_code;
+       const char *    e2p_description;
+       char            prompt;
+       int             flags;
+       problem_t       second_code;
+};
+
+struct latch_descr {
+       int             latch_code;
+       problem_t       question;
+       problem_t       end_message;
+       int             flags;
+};
+
+/*
+ * These are the prompts which are used to ask the user if they want
+ * to fix a problem.
+ */
+static const char *const prompt[] = {
+       N_("(no prompt)"),      /* 0 */
+       N_("Fix"),              /* 1 */
+       N_("Clear"),            /* 2 */
+       N_("Relocate"),         /* 3 */
+       N_("Allocate"),         /* 4 */
+       N_("Expand"),           /* 5 */
+       N_("Connect to /lost+found"), /* 6 */
+       N_("Create"),           /* 7 */
+       N_("Salvage"),          /* 8 */
+       N_("Truncate"),         /* 9 */
+       N_("Clear inode"),      /* 10 */
+       N_("Abort"),            /* 11 */
+       N_("Split"),            /* 12 */
+       N_("Continue"),         /* 13 */
+       N_("Clone multiply-claimed blocks"), /* 14 */
+       N_("Delete file"),      /* 15 */
+       N_("Suppress messages"),/* 16 */
+       N_("Unlink"),           /* 17 */
+       N_("Clear HTree index"),/* 18 */
+       N_("Recreate"),         /* 19 */
+       "",                     /* 20 */
+};
+
+/*
+ * These messages are printed when we are preen mode and we will be
+ * automatically fixing the problem.
+ */
+static const char *const preen_msg[] = {
+       N_("(NONE)"),           /* 0 */
+       N_("FIXED"),            /* 1 */
+       N_("CLEARED"),          /* 2 */
+       N_("RELOCATED"),        /* 3 */
+       N_("ALLOCATED"),        /* 4 */
+       N_("EXPANDED"),         /* 5 */
+       N_("RECONNECTED"),      /* 6 */
+       N_("CREATED"),          /* 7 */
+       N_("SALVAGED"),         /* 8 */
+       N_("TRUNCATED"),        /* 9 */
+       N_("INODE CLEARED"),    /* 10 */
+       N_("ABORTED"),          /* 11 */
+       N_("SPLIT"),            /* 12 */
+       N_("CONTINUING"),       /* 13 */
+       N_("MULTIPLY-CLAIMED BLOCKS CLONED"), /* 14 */
+       N_("FILE DELETED"),     /* 15 */
+       N_("SUPPRESSED"),       /* 16 */
+       N_("UNLINKED"),         /* 17 */
+       N_("HTREE INDEX CLEARED"),/* 18 */
+       N_("WILL RECREATE"),    /* 19 */
+       "",                     /* 20 */
+};
+
+static const struct e2fsck_problem problem_table[] = {
+
+       /* Pre-Pass 1 errors */
+
+       /* Block bitmap not in group */
+       { PR_0_BB_NOT_GROUP, N_("@b @B for @g %g is not in @g.  (@b %b)\n"),
+         PROMPT_RELOCATE, PR_LATCH_RELOC },
+
+       /* Inode bitmap not in group */
+       { PR_0_IB_NOT_GROUP, N_("@i @B for @g %g is not in @g.  (@b %b)\n"),
+         PROMPT_RELOCATE, PR_LATCH_RELOC },
+
+       /* Inode table not in group */
+       { PR_0_ITABLE_NOT_GROUP,
+         N_("@i table for @g %g is not in @g.  (@b %b)\n"
+         "WARNING: SEVERE DATA LOSS POSSIBLE.\n"),
+         PROMPT_RELOCATE, PR_LATCH_RELOC },
+
+       /* Superblock corrupt */
+       { PR_0_SB_CORRUPT,
+         N_("\nThe @S could not be read or does not describe a correct ext2\n"
+         "@f.  If the @v is valid and it really contains an ext2\n"
+         "@f (and not swap or ufs or something else), then the @S\n"
+         "is corrupt, and you might try running e2fsck with an alternate @S:\n"
+         "    e2fsck -b %S <@v>\n\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Filesystem size is wrong */
+       { PR_0_FS_SIZE_WRONG,
+         N_("The @f size (according to the @S) is %b @bs\n"
+         "The physical size of the @v is %c @bs\n"
+         "Either the @S or the partition table is likely to be corrupt!\n"),
+         PROMPT_ABORT, 0 },
+
+       /* Fragments not supported */
+       { PR_0_NO_FRAGMENTS,
+         N_("@S @b_size = %b, fragsize = %c.\n"
+         "This version of e2fsck does not support fragment sizes different\n"
+         "from the @b size.\n"),
+         PROMPT_NONE, PR_FATAL },
+
+         /* Bad blocks_per_group */
+       { PR_0_BLOCKS_PER_GROUP,
+         N_("@S @bs_per_group = %b, should have been %c\n"),
+         PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT },
+
+       /* Bad first_data_block */
+       { PR_0_FIRST_DATA_BLOCK,
+         N_("@S first_data_@b = %b, should have been %c\n"),
+         PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT },
+
+       /* Adding UUID to filesystem */
+       { PR_0_ADD_UUID,
+         N_("@f did not have a UUID; generating one.\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Relocate hint */
+       { PR_0_RELOCATE_HINT,
+         N_("Note: if several inode or block bitmap blocks or part\n"
+         "of the inode table require relocation, you may wish to try\n"
+         "running e2fsck with the '-b %S' option first.  The problem\n"
+         "may lie only with the primary block group descriptors, and\n"
+         "the backup block group descriptors may be OK.\n\n"),
+         PROMPT_NONE, PR_PREEN_OK | PR_NOCOLLATE },
+
+       /* Miscellaneous superblock corruption */
+       { PR_0_MISC_CORRUPT_SUPER,
+         N_("Corruption found in @S.  (%s = %N).\n"),
+         PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT },
+
+       /* Error determing physical device size of filesystem */
+       { PR_0_GETSIZE_ERROR,
+         N_("Error determining size of the physical @v: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Inode count in superblock is incorrect */
+       { PR_0_INODE_COUNT_WRONG,
+         N_("@i count in @S is %i, @s %j.\n"),
+         PROMPT_FIX, 0 },
+
+       { PR_0_HURD_CLEAR_FILETYPE,
+         N_("The Hurd does not support the filetype feature.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Journal inode is invalid */
+       { PR_0_JOURNAL_BAD_INODE,
+         N_("@S has an @n ext3 @j (@i %i).\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* The external journal has (unsupported) multiple filesystems */
+       { PR_0_JOURNAL_UNSUPP_MULTIFS,
+         N_("External @j has multiple @f users (unsupported).\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Can't find external journal */
+       { PR_0_CANT_FIND_JOURNAL,
+         N_("Can't find external @j\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* External journal has bad superblock */
+       { PR_0_EXT_JOURNAL_BAD_SUPER,
+         N_("External @j has bad @S\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Superblock has a bad journal UUID */
+       { PR_0_JOURNAL_BAD_UUID,
+         N_("External @j does not support this @f\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Journal has an unknown superblock type */
+       { PR_0_JOURNAL_UNSUPP_SUPER,
+         N_("Ext3 @j @S is unknown type %N (unsupported).\n"
+            "It is likely that your copy of e2fsck is old and/or doesn't "
+            "support this @j format.\n"
+            "It is also possible the @j @S is corrupt.\n"),
+         PROMPT_ABORT, PR_NO_OK | PR_AFTER_CODE, PR_0_JOURNAL_BAD_SUPER },
+
+       /* Journal superblock is corrupt */
+       { PR_0_JOURNAL_BAD_SUPER,
+         N_("Ext3 @j @S is corrupt.\n"),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Superblock flag should be cleared */
+       { PR_0_JOURNAL_HAS_JOURNAL,
+         N_("@S doesn't have has_@j flag, but has ext3 @j %s.\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Superblock flag is incorrect */
+       { PR_0_JOURNAL_RECOVER_SET,
+         N_("@S has ext3 needs_recovery flag set, but no @j.\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Journal has data, but recovery flag is clear */
+       { PR_0_JOURNAL_RECOVERY_CLEAR,
+         N_("ext3 recovery flag is clear, but @j has data.\n"),
+         PROMPT_NONE, 0 },
+
+       /* Ask if we should clear the journal */
+       { PR_0_JOURNAL_RESET_JOURNAL,
+         N_("Clear @j"),
+         PROMPT_NULL, PR_PREEN_NOMSG },
+
+       /* Ask if we should run the journal anyway */
+       { PR_0_JOURNAL_RUN,
+         N_("Run @j anyway"),
+         PROMPT_NULL, 0 },
+
+       /* Run the journal by default */
+       { PR_0_JOURNAL_RUN_DEFAULT,
+         N_("Recovery flag not set in backup @S, so running @j anyway.\n"),
+         PROMPT_NONE, 0 },
+
+       /* Clearing orphan inode */
+       { PR_0_ORPHAN_CLEAR_INODE,
+         N_("%s @o @i %i (uid=%Iu, gid=%Ig, mode=%Im, size=%Is)\n"),
+         PROMPT_NONE, 0 },
+
+       /* Illegal block found in orphaned inode */
+       { PR_0_ORPHAN_ILLEGAL_BLOCK_NUM,
+          N_("@I @b #%B (%b) found in @o @i %i.\n"),
+         PROMPT_NONE, 0 },
+
+       /* Already cleared block found in orphaned inode */
+       { PR_0_ORPHAN_ALREADY_CLEARED_BLOCK,
+          N_("Already cleared @b #%B (%b) found in @o @i %i.\n"),
+         PROMPT_NONE, 0 },
+
+       /* Illegal orphan inode in superblock */
+       { PR_0_ORPHAN_ILLEGAL_HEAD_INODE,
+         N_("@I @o @i %i in @S.\n"),
+         PROMPT_NONE, 0 },
+
+       /* Illegal inode in orphaned inode list */
+       { PR_0_ORPHAN_ILLEGAL_INODE,
+         N_("@I @i %i in @o @i list.\n"),
+         PROMPT_NONE, 0 },
+
+       /* Filesystem revision is 0, but feature flags are set */
+       { PR_0_FS_REV_LEVEL,
+         N_("@f has feature flag(s) set, but is a revision 0 @f.  "),
+         PROMPT_FIX, PR_PREEN_OK | PR_NO_OK },
+
+       /* Journal superblock has an unknown read-only feature flag set */
+       { PR_0_JOURNAL_UNSUPP_ROCOMPAT,
+         N_("Ext3 @j @S has an unknown read-only feature flag set.\n"),
+         PROMPT_ABORT, 0 },
+
+       /* Journal superblock has an unknown incompatible feature flag set */
+       { PR_0_JOURNAL_UNSUPP_INCOMPAT,
+         N_("Ext3 @j @S has an unknown incompatible feature flag set.\n"),
+         PROMPT_ABORT, 0 },
+
+       /* Journal has unsupported version number */
+       { PR_0_JOURNAL_UNSUPP_VERSION,
+         N_("@j version not supported by this e2fsck.\n"),
+         PROMPT_ABORT, 0 },
+
+       /* Moving journal to hidden file */
+       { PR_0_MOVE_JOURNAL,
+         N_("Moving @j from /%s to hidden @i.\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error moving journal to hidden file */
+       { PR_0_ERR_MOVE_JOURNAL,
+         N_("Error moving @j: %m\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Clearing V2 journal superblock */
+       { PR_0_CLEAR_V2_JOURNAL,
+         N_("Found @n V2 @j @S fields (from V1 @j).\n"
+            "Clearing fields beyond the V1 @j @S...\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Backup journal inode blocks */
+       { PR_0_BACKUP_JNL,
+         N_("Backing up @j @i @b information.\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Reserved blocks w/o resize_inode */
+       { PR_0_NONZERO_RESERVED_GDT_BLOCKS,
+         N_("@f does not have resize_@i enabled, but s_reserved_gdt_@bs\n"
+            "is %N; @s zero.  "),
+         PROMPT_FIX, 0 },
+
+       /* Resize_inode not enabled, but resize inode is non-zero */
+       { PR_0_CLEAR_RESIZE_INODE,
+         N_("Resize_@i not enabled, but the resize @i is non-zero.  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Resize inode invalid */
+       { PR_0_RESIZE_INODE_INVALID,
+         N_("Resize @i not valid.  "),
+         PROMPT_RECREATE, 0 },
+
+       /* Pass 1 errors */
+
+       /* Pass 1: Checking inodes, blocks, and sizes */
+       { PR_1_PASS_HEADER,
+         N_("Pass 1: Checking @is, @bs, and sizes\n"),
+         PROMPT_NONE, 0 },
+
+       /* Root directory is not an inode */
+       { PR_1_ROOT_NO_DIR, N_("@r is not a @d.  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Root directory has dtime set */
+       { PR_1_ROOT_DTIME,
+         N_("@r has dtime set (probably due to old mke2fs).  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Reserved inode has bad mode */
+       { PR_1_RESERVED_BAD_MODE,
+         N_("Reserved @i %i (%Q) has @n mode.  "),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Deleted inode has zero dtime */
+       { PR_1_ZERO_DTIME,
+         N_("@D @i %i has zero dtime.  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Inode in use, but dtime set */
+       { PR_1_SET_DTIME,
+         N_("@i %i is in use, but has dtime set.  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Zero-length directory */
+       { PR_1_ZERO_LENGTH_DIR,
+         N_("@i %i is a @z @d.  "),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Block bitmap conflicts with some other fs block */
+       { PR_1_BB_CONFLICT,
+         N_("@g %g's @b @B at %b @C.\n"),
+         PROMPT_RELOCATE, 0 },
+
+       /* Inode bitmap conflicts with some other fs block */
+       { PR_1_IB_CONFLICT,
+         N_("@g %g's @i @B at %b @C.\n"),
+         PROMPT_RELOCATE, 0 },
+
+       /* Inode table conflicts with some other fs block */
+       { PR_1_ITABLE_CONFLICT,
+         N_("@g %g's @i table at %b @C.\n"),
+         PROMPT_RELOCATE, 0 },
+
+       /* Block bitmap is on a bad block */
+       { PR_1_BB_BAD_BLOCK,
+         N_("@g %g's @b @B (%b) is bad.  "),
+         PROMPT_RELOCATE, 0 },
+
+       /* Inode bitmap is on a bad block */
+       { PR_1_IB_BAD_BLOCK,
+         N_("@g %g's @i @B (%b) is bad.  "),
+         PROMPT_RELOCATE, 0 },
+
+       /* Inode has incorrect i_size */
+       { PR_1_BAD_I_SIZE,
+         N_("@i %i, i_size is %Is, @s %N.  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Inode has incorrect i_blocks */
+       { PR_1_BAD_I_BLOCKS,
+         N_("@i %i, i_@bs is %Ib, @s %N.  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Illegal blocknumber in inode */
+       { PR_1_ILLEGAL_BLOCK_NUM,
+         N_("@I @b #%B (%b) in @i %i.  "),
+         PROMPT_CLEAR, PR_LATCH_BLOCK },
+
+       /* Block number overlaps fs metadata */
+       { PR_1_BLOCK_OVERLAPS_METADATA,
+         N_("@b #%B (%b) overlaps @f metadata in @i %i.  "),
+         PROMPT_CLEAR, PR_LATCH_BLOCK },
+
+       /* Inode has illegal blocks (latch question) */
+       { PR_1_INODE_BLOCK_LATCH,
+         N_("@i %i has illegal @b(s).  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Too many bad blocks in inode */
+       { PR_1_TOO_MANY_BAD_BLOCKS,
+         N_("Too many illegal @bs in @i %i.\n"),
+         PROMPT_CLEAR_INODE, PR_NO_OK },
+
+       /* Illegal block number in bad block inode */
+       { PR_1_BB_ILLEGAL_BLOCK_NUM,
+         N_("@I @b #%B (%b) in bad @b @i.  "),
+         PROMPT_CLEAR, PR_LATCH_BBLOCK },
+
+       /* Bad block inode has illegal blocks (latch question) */
+       { PR_1_INODE_BBLOCK_LATCH,
+         N_("Bad @b @i has illegal @b(s).  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Duplicate or bad blocks in use! */
+       { PR_1_DUP_BLOCKS_PREENSTOP,
+         N_("Duplicate or bad @b in use!\n"),
+         PROMPT_NONE, 0 },
+
+       /* Bad block used as bad block indirect block */
+       { PR_1_BBINODE_BAD_METABLOCK,
+         N_("Bad @b %b used as bad @b @i indirect @b.  "),
+         PROMPT_CLEAR, PR_LATCH_BBLOCK },
+
+       /* Inconsistency can't be fixed prompt */
+       { PR_1_BBINODE_BAD_METABLOCK_PROMPT,
+         N_("\nThe bad @b @i has probably been corrupted.  You probably\n"
+            "should stop now and run ""e2fsck -c"" to scan for bad blocks\n"
+            "in the @f.\n"),
+         PROMPT_CONTINUE, PR_PREEN_NOMSG },
+
+       /* Bad primary block */
+       { PR_1_BAD_PRIMARY_BLOCK,
+         N_("\nIf the @b is really bad, the @f cannot be fixed.\n"),
+         PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK_PROMPT },
+
+       /* Bad primary block prompt */
+       { PR_1_BAD_PRIMARY_BLOCK_PROMPT,
+         N_("You can remove this @b from the bad @b list and hope\n"
+            "that the @b is really OK.  But there are no guarantees.\n\n"),
+         PROMPT_CLEAR, PR_PREEN_NOMSG },
+
+       /* Bad primary superblock */
+       { PR_1_BAD_PRIMARY_SUPERBLOCK,
+         N_("The primary @S (%b) is on the bad @b list.\n"),
+         PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK },
+
+       /* Bad primary block group descriptors */
+       { PR_1_BAD_PRIMARY_GROUP_DESCRIPTOR,
+         N_("Block %b in the primary @g descriptors "
+         "is on the bad @b list\n"),
+         PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK },
+
+       /* Bad superblock in group */
+       { PR_1_BAD_SUPERBLOCK,
+         N_("Warning: Group %g's @S (%b) is bad.\n"),
+         PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Bad block group descriptors in group */
+       { PR_1_BAD_GROUP_DESCRIPTORS,
+         N_("Warning: Group %g's copy of the @g descriptors has a bad "
+         "@b (%b).\n"),
+         PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Block claimed for no reason */
+       { PR_1_PROGERR_CLAIMED_BLOCK,
+         N_("Programming error?  @b #%b claimed for no reason in "
+         "process_bad_@b.\n"),
+         PROMPT_NONE, PR_PREEN_OK },
+
+       /* Error allocating blocks for relocating metadata */
+       { PR_1_RELOC_BLOCK_ALLOCATE,
+         N_("@A %N contiguous @b(s) in @b @g %g for %s: %m\n"),
+         PROMPT_NONE, PR_PREEN_OK },
+
+       /* Error allocating block buffer during relocation process */
+       { PR_1_RELOC_MEMORY_ALLOCATE,
+         N_("@A @b buffer for relocating %s\n"),
+         PROMPT_NONE, PR_PREEN_OK },
+
+       /* Relocating metadata group information from X to Y */
+       { PR_1_RELOC_FROM_TO,
+         N_("Relocating @g %g's %s from %b to %c...\n"),
+         PROMPT_NONE, PR_PREEN_OK },
+
+       /* Relocating metatdata group information to X */
+       { PR_1_RELOC_TO,
+         N_("Relocating @g %g's %s to %c...\n"), /* xgettext:no-c-format */
+         PROMPT_NONE, PR_PREEN_OK },
+
+       /* Block read error during relocation process */
+       { PR_1_RELOC_READ_ERR,
+         N_("Warning: could not read @b %b of %s: %m\n"),
+         PROMPT_NONE, PR_PREEN_OK },
+
+       /* Block write error during relocation process */
+       { PR_1_RELOC_WRITE_ERR,
+         N_("Warning: could not write @b %b for %s: %m\n"),
+         PROMPT_NONE, PR_PREEN_OK },
+
+       /* Error allocating inode bitmap */
+       { PR_1_ALLOCATE_IBITMAP_ERROR,
+         N_("@A @i @B (%N): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error allocating block bitmap */
+       { PR_1_ALLOCATE_BBITMAP_ERROR,
+         N_("@A @b @B (%N): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error allocating icount structure */
+       { PR_1_ALLOCATE_ICOUNT,
+         N_("@A icount link information: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error allocating dbcount */
+       { PR_1_ALLOCATE_DBCOUNT,
+         N_("@A @d @b array: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error while scanning inodes */
+       { PR_1_ISCAN_ERROR,
+         N_("Error while scanning @is (%i): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error while iterating over blocks */
+       { PR_1_BLOCK_ITERATE,
+         N_("Error while iterating over @bs in @i %i: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error while storing inode count information */
+       { PR_1_ICOUNT_STORE,
+         N_("Error storing @i count information (@i=%i, count=%N): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error while storing directory block information */
+       { PR_1_ADD_DBLOCK,
+         N_("Error storing @d @b information "
+         "(@i=%i, @b=%b, num=%N): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error while reading inode (for clearing) */
+       { PR_1_READ_INODE,
+         N_("Error reading @i %i: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Suppress messages prompt */
+       { PR_1_SUPPRESS_MESSAGES, "", PROMPT_SUPPRESS, PR_NO_OK },
+
+       /* Imagic flag set on an inode when filesystem doesn't support it */
+       { PR_1_SET_IMAGIC,
+         N_("@i %i has imagic flag set.  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Immutable flag set on a device or socket inode */
+       { PR_1_SET_IMMUTABLE,
+         N_("Special (@v/socket/fifo/symlink) file (@i %i) has immutable\n"
+            "or append-only flag set.  "),
+         PROMPT_CLEAR, PR_PREEN_OK | PR_PREEN_NO | PR_NO_OK },
+
+       /* Compression flag set on an inode when filesystem doesn't support it */
+       { PR_1_COMPR_SET,
+         N_("@i %i has @cion flag set on @f without @cion support.  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Non-zero size for device, fifo or socket inode */
+       { PR_1_SET_NONZSIZE,
+         N_("Special (@v/socket/fifo) @i %i has non-zero size.  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Filesystem revision is 0, but feature flags are set */
+       { PR_1_FS_REV_LEVEL,
+         N_("@f has feature flag(s) set, but is a revision 0 @f.  "),
+         PROMPT_FIX, PR_PREEN_OK | PR_NO_OK },
+
+       /* Journal inode is not in use, but contains data */
+       { PR_1_JOURNAL_INODE_NOT_CLEAR,
+         N_("@j @i is not in use, but contains data.  "),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Journal has bad mode */
+       { PR_1_JOURNAL_BAD_MODE,
+         N_("@j is not regular file.  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Deal with inodes that were part of orphan linked list */
+       { PR_1_LOW_DTIME,
+         N_("@i %i was part of the @o @i list.  "),
+         PROMPT_FIX, PR_LATCH_LOW_DTIME, 0 },
+
+       /* Deal with inodes that were part of corrupted orphan linked
+          list (latch question) */
+       { PR_1_ORPHAN_LIST_REFUGEES,
+         N_("@is that were part of a corrupted orphan linked list found.  "),
+         PROMPT_FIX, 0 },
+
+       /* Error allocating refcount structure */
+       { PR_1_ALLOCATE_REFCOUNT,
+         N_("@A refcount structure (%N): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error reading extended attribute block */
+       { PR_1_READ_EA_BLOCK,
+         N_("Error reading @a @b %b for @i %i.  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Invalid extended attribute block */
+       { PR_1_BAD_EA_BLOCK,
+         N_("@i %i has a bad @a @b %b.  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Error reading Extended Attribute block while fixing refcount */
+       { PR_1_EXTATTR_READ_ABORT,
+         N_("Error reading @a @b %b (%m).  "),
+         PROMPT_ABORT, 0 },
+
+       /* Extended attribute reference count incorrect */
+       { PR_1_EXTATTR_REFCOUNT,
+         N_("@a @b %b has reference count %B, @s %N.  "),
+         PROMPT_FIX, 0 },
+
+       /* Error writing Extended Attribute block while fixing refcount */
+       { PR_1_EXTATTR_WRITE,
+         N_("Error writing @a @b %b (%m).  "),
+         PROMPT_ABORT, 0 },
+
+       /* Multiple EA blocks not supported */
+       { PR_1_EA_MULTI_BLOCK,
+         N_("@a @b %b has h_@bs > 1.  "),
+         PROMPT_CLEAR, 0},
+
+       /* Error allocating EA region allocation structure */
+       { PR_1_EA_ALLOC_REGION,
+         N_("@A @a @b %b.  "),
+         PROMPT_ABORT, 0},
+
+       /* Error EA allocation collision */
+       { PR_1_EA_ALLOC_COLLISION,
+         N_("@a @b %b is corrupt (allocation collision).  "),
+         PROMPT_CLEAR, 0},
+
+       /* Bad extended attribute name */
+       { PR_1_EA_BAD_NAME,
+         N_("@a @b %b is corrupt (@n name).  "),
+         PROMPT_CLEAR, 0},
+
+       /* Bad extended attribute value */
+       { PR_1_EA_BAD_VALUE,
+         N_("@a @b %b is corrupt (@n value).  "),
+         PROMPT_CLEAR, 0},
+
+       /* Inode too big (latch question) */
+       { PR_1_INODE_TOOBIG,
+         N_("@i %i is too big.  "), PROMPT_TRUNCATE, 0 },
+
+       /* Directory too big */
+       { PR_1_TOOBIG_DIR,
+         N_("@b #%B (%b) causes @d to be too big.  "),
+         PROMPT_CLEAR, PR_LATCH_TOOBIG },
+
+       /* Regular file too big */
+       { PR_1_TOOBIG_REG,
+         N_("@b #%B (%b) causes file to be too big.  "),
+         PROMPT_CLEAR, PR_LATCH_TOOBIG },
+
+       /* Symlink too big */
+       { PR_1_TOOBIG_SYMLINK,
+         N_("@b #%B (%b) causes symlink to be too big.  "),
+         PROMPT_CLEAR, PR_LATCH_TOOBIG },
+
+       /* INDEX_FL flag set on a non-HTREE filesystem */
+       { PR_1_HTREE_SET,
+         N_("@i %i has INDEX_FL flag set on @f without htree support.\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* INDEX_FL flag set on a non-directory */
+       { PR_1_HTREE_NODIR,
+         N_("@i %i has INDEX_FL flag set but is not a @d.\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* Invalid root node in HTREE directory */
+       { PR_1_HTREE_BADROOT,
+         N_("@h %i has an @n root node.\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* Unsupported hash version in HTREE directory */
+       { PR_1_HTREE_HASHV,
+         N_("@h %i has an unsupported hash version (%N)\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* Incompatible flag in HTREE root node */
+       { PR_1_HTREE_INCOMPAT,
+         N_("@h %i uses an incompatible htree root node flag.\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* HTREE too deep */
+       { PR_1_HTREE_DEPTH,
+         N_("@h %i has a tree depth (%N) which is too big\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* Bad block has indirect block that conflicts with filesystem block */
+       { PR_1_BB_FS_BLOCK,
+         N_("Bad @b @i has an indirect @b (%b) that conflicts with\n"
+            "@f metadata.  "),
+         PROMPT_CLEAR, PR_LATCH_BBLOCK },
+
+       /* Resize inode failed */
+       { PR_1_RESIZE_INODE_CREATE,
+         N_("Resize @i (re)creation failed: %m."),
+         PROMPT_ABORT, 0 },
+
+       /* invalid inode->i_extra_isize */
+       { PR_1_EXTRA_ISIZE,
+         N_("@i %i has a extra size (%IS) which is @n\n"),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* invalid ea entry->e_name_len */
+       { PR_1_ATTR_NAME_LEN,
+         N_("@a in @i %i has a namelen (%N) which is @n\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* invalid ea entry->e_value_size */
+       { PR_1_ATTR_VALUE_SIZE,
+         N_("@a in @i %i has a value size (%N) which is @n\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* invalid ea entry->e_value_offs */
+       { PR_1_ATTR_VALUE_OFFSET,
+         N_("@a in @i %i has a value offset (%N) which is @n\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* invalid ea entry->e_value_block */
+       { PR_1_ATTR_VALUE_BLOCK,
+         N_("@a in @i %i has a value @b (%N) which is @n (must be 0)\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* invalid ea entry->e_hash */
+       { PR_1_ATTR_HASH,
+         N_("@a in @i %i has a hash (%N) which is @n (must be 0)\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Pass 1b errors */
+
+       /* Pass 1B: Rescan for duplicate/bad blocks */
+       { PR_1B_PASS_HEADER,
+         N_("\nRunning additional passes to resolve @bs claimed by more than one @i...\n"
+         "Pass 1B: Rescanning for @m @bs\n"),
+         PROMPT_NONE, 0 },
+
+       /* Duplicate/bad block(s) header */
+       { PR_1B_DUP_BLOCK_HEADER,
+         N_("@m @b(s) in @i %i:"),
+         PROMPT_NONE, 0 },
+
+       /* Duplicate/bad block(s) in inode */
+       { PR_1B_DUP_BLOCK,
+         " %b",
+         PROMPT_NONE, PR_LATCH_DBLOCK | PR_PREEN_NOHDR },
+
+       /* Duplicate/bad block(s) end */
+       { PR_1B_DUP_BLOCK_END,
+         "\n",
+         PROMPT_NONE, PR_PREEN_NOHDR },
+
+       /* Error while scanning inodes */
+       { PR_1B_ISCAN_ERROR,
+         N_("Error while scanning inodes (%i): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error allocating inode bitmap */
+       { PR_1B_ALLOCATE_IBITMAP_ERROR,
+         N_("@A @i @B (@i_dup_map): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error while iterating over blocks */
+       { PR_1B_BLOCK_ITERATE,
+         N_("Error while iterating over @bs in @i %i (%s): %m\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error adjusting EA refcount */
+       { PR_1B_ADJ_EA_REFCOUNT,
+         N_("Error adjusting refcount for @a @b %b (@i %i): %m\n"),
+         PROMPT_NONE, 0 },
+
+
+       /* Pass 1C: Scan directories for inodes with multiply-claimed blocks. */
+       { PR_1C_PASS_HEADER,
+         N_("Pass 1C: Scanning directories for @is with @m @bs.\n"),
+         PROMPT_NONE, 0 },
+
+
+       /* Pass 1D: Reconciling multiply-claimed blocks */
+       { PR_1D_PASS_HEADER,
+         N_("Pass 1D: Reconciling @m @bs\n"),
+         PROMPT_NONE, 0 },
+
+       /* File has duplicate blocks */
+       { PR_1D_DUP_FILE,
+         N_("File %Q (@i #%i, mod time %IM)\n"
+         "  has %B @m @b(s), shared with %N file(s):\n"),
+         PROMPT_NONE, 0 },
+
+       /* List of files sharing duplicate blocks */
+       { PR_1D_DUP_FILE_LIST,
+         N_("\t%Q (@i #%i, mod time %IM)\n"),
+         PROMPT_NONE, 0 },
+
+       /* File sharing blocks with filesystem metadata  */
+       { PR_1D_SHARE_METADATA,
+         N_("\t<@f metadata>\n"),
+         PROMPT_NONE, 0 },
+
+       /* Report of how many duplicate/bad inodes */
+       { PR_1D_NUM_DUP_INODES,
+         N_("(There are %N @is containing @m @bs.)\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Duplicated blocks already reassigned or cloned. */
+       { PR_1D_DUP_BLOCKS_DEALT,
+         N_("@m @bs already reassigned or cloned.\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Clone duplicate/bad blocks? */
+       { PR_1D_CLONE_QUESTION,
+         "", PROMPT_CLONE, PR_NO_OK },
+
+       /* Delete file? */
+       { PR_1D_DELETE_QUESTION,
+         "", PROMPT_DELETE, 0 },
+
+       /* Couldn't clone file (error) */
+       { PR_1D_CLONE_ERROR,
+         N_("Couldn't clone file: %m\n"), PROMPT_NONE, 0 },
+
+       /* Pass 2 errors */
+
+       /* Pass 2: Checking directory structure */
+       { PR_2_PASS_HEADER,
+         N_("Pass 2: Checking @d structure\n"),
+         PROMPT_NONE, 0 },
+
+       /* Bad inode number for '.' */
+       { PR_2_BAD_INODE_DOT,
+         N_("@n @i number for '.' in @d @i %i.\n"),
+         PROMPT_FIX, 0 },
+
+       /* Directory entry has bad inode number */
+       { PR_2_BAD_INO,
+         N_("@E has @n @i #: %Di.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Directory entry has deleted or unused inode */
+       { PR_2_UNUSED_INODE,
+         N_("@E has @D/unused @i %Di.  "),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Directry entry is link to '.' */
+       { PR_2_LINK_DOT,
+         N_("@E @L to '.'  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Directory entry points to inode now located in a bad block */
+       { PR_2_BB_INODE,
+         N_("@E points to @i (%Di) located in a bad @b.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Directory entry contains a link to a directory */
+       { PR_2_LINK_DIR,
+         N_("@E @L to @d %P (%Di).\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Directory entry contains a link to the root directry */
+       { PR_2_LINK_ROOT,
+         N_("@E @L to the @r.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Directory entry has illegal characters in its name */
+       { PR_2_BAD_NAME,
+         N_("@E has illegal characters in its name.\n"),
+         PROMPT_FIX, 0 },
+
+       /* Missing '.' in directory inode */
+       { PR_2_MISSING_DOT,
+         N_("Missing '.' in @d @i %i.\n"),
+         PROMPT_FIX, 0 },
+
+       /* Missing '..' in directory inode */
+       { PR_2_MISSING_DOT_DOT,
+         N_("Missing '..' in @d @i %i.\n"),
+         PROMPT_FIX, 0 },
+
+       /* First entry in directory inode doesn't contain '.' */
+       { PR_2_1ST_NOT_DOT,
+         N_("First @e '%Dn' (@i=%Di) in @d @i %i (%p) @s '.'\n"),
+         PROMPT_FIX, 0 },
+
+       /* Second entry in directory inode doesn't contain '..' */
+       { PR_2_2ND_NOT_DOT_DOT,
+         N_("Second @e '%Dn' (@i=%Di) in @d @i %i @s '..'\n"),
+         PROMPT_FIX, 0 },
+
+       /* i_faddr should be zero */
+       { PR_2_FADDR_ZERO,
+         N_("i_faddr @F %IF, @s zero.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* i_file_acl should be zero */
+       { PR_2_FILE_ACL_ZERO,
+         N_("i_file_acl @F %If, @s zero.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* i_dir_acl should be zero */
+       { PR_2_DIR_ACL_ZERO,
+         N_("i_dir_acl @F %Id, @s zero.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* i_frag should be zero */
+       { PR_2_FRAG_ZERO,
+         N_("i_frag @F %N, @s zero.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* i_fsize should be zero */
+       { PR_2_FSIZE_ZERO,
+         N_("i_fsize @F %N, @s zero.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* inode has bad mode */
+       { PR_2_BAD_MODE,
+         N_("@i %i (%Q) has @n mode (%Im).\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* directory corrupted */
+       { PR_2_DIR_CORRUPTED,
+         N_("@d @i %i, @b %B, offset %N: @d corrupted\n"),
+         PROMPT_SALVAGE, 0 },
+
+       /* filename too long */
+       { PR_2_FILENAME_LONG,
+         N_("@d @i %i, @b %B, offset %N: filename too long\n"),
+         PROMPT_TRUNCATE, 0 },
+
+       /* Directory inode has a missing block (hole) */
+       { PR_2_DIRECTORY_HOLE,
+         N_("@d @i %i has an unallocated @b #%B.  "),
+         PROMPT_ALLOCATE, 0 },
+
+       /* '.' is not NULL terminated */
+       { PR_2_DOT_NULL_TERM,
+         N_("'.' @d @e in @d @i %i is not NULL terminated\n"),
+         PROMPT_FIX, 0 },
+
+       /* '..' is not NULL terminated */
+       { PR_2_DOT_DOT_NULL_TERM,
+         N_("'..' @d @e in @d @i %i is not NULL terminated\n"),
+         PROMPT_FIX, 0 },
+
+       /* Illegal character device inode */
+       { PR_2_BAD_CHAR_DEV,
+         N_("@i %i (%Q) is an @I character @v.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Illegal block device inode */
+       { PR_2_BAD_BLOCK_DEV,
+         N_("@i %i (%Q) is an @I @b @v.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Duplicate '.' entry */
+       { PR_2_DUP_DOT,
+         N_("@E is duplicate '.' @e.\n"),
+         PROMPT_FIX, 0 },
+
+       /* Duplicate '..' entry */
+       { PR_2_DUP_DOT_DOT,
+         N_("@E is duplicate '..' @e.\n"),
+         PROMPT_FIX, 0 },
+
+       /* Internal error: couldn't find dir_info */
+       { PR_2_NO_DIRINFO,
+         N_("Internal error: cannot find dir_info for %i.\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Final rec_len is wrong */
+       { PR_2_FINAL_RECLEN,
+         N_("@E has rec_len of %Dr, @s %N.\n"),
+         PROMPT_FIX, 0 },
+
+       /* Error allocating icount structure */
+       { PR_2_ALLOCATE_ICOUNT,
+         N_("@A icount structure: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error iterating over directory blocks */
+       { PR_2_DBLIST_ITERATE,
+         N_("Error iterating over @d @bs: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error reading directory block */
+       { PR_2_READ_DIRBLOCK,
+         N_("Error reading @d @b %b (@i %i): %m\n"),
+         PROMPT_CONTINUE, 0 },
+
+       /* Error writing directory block */
+       { PR_2_WRITE_DIRBLOCK,
+         N_("Error writing @d @b %b (@i %i): %m\n"),
+         PROMPT_CONTINUE, 0 },
+
+       /* Error allocating new directory block */
+       { PR_2_ALLOC_DIRBOCK,
+         N_("@A new @d @b for @i %i (%s): %m\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error deallocating inode */
+       { PR_2_DEALLOC_INODE,
+         N_("Error deallocating @i %i: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Directory entry for '.' is big.  Split? */
+       { PR_2_SPLIT_DOT,
+         N_("@d @e for '.' is big.  "),
+         PROMPT_SPLIT, PR_NO_OK },
+
+       /* Illegal FIFO inode */
+       { PR_2_BAD_FIFO,
+         N_("@i %i (%Q) is an @I FIFO.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Illegal socket inode */
+       { PR_2_BAD_SOCKET,
+         N_("@i %i (%Q) is an @I socket.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Directory filetype not set */
+       { PR_2_SET_FILETYPE,
+         N_("Setting filetype for @E to %N.\n"),
+         PROMPT_NONE, PR_PREEN_OK | PR_NO_OK | PR_NO_NOMSG },
+
+       /* Directory filetype incorrect */
+       { PR_2_BAD_FILETYPE,
+         N_("@E has an incorrect filetype (was %Dt, @s %N).\n"),
+         PROMPT_FIX, 0 },
+
+       /* Directory filetype set on filesystem */
+       { PR_2_CLEAR_FILETYPE,
+         N_("@E has filetype set.\n"),
+         PROMPT_CLEAR, PR_PREEN_OK },
+
+       /* Directory filename is null */
+       { PR_2_NULL_NAME,
+         N_("@E has a @z name.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Invalid symlink */
+       { PR_2_INVALID_SYMLINK,
+         N_("Symlink %Q (@i #%i) is @n.\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* i_file_acl (extended attribute block) is bad */
+       { PR_2_FILE_ACL_BAD,
+         N_("@a @b @F @n (%If).\n"),
+         PROMPT_CLEAR, 0 },
+
+       /* Filesystem contains large files, but has no such flag in sb */
+       { PR_2_FEATURE_LARGE_FILES,
+         N_("@f contains large files, but lacks LARGE_FILE flag in @S.\n"),
+         PROMPT_FIX, 0 },
+
+       /* Node in HTREE directory not referenced */
+       { PR_2_HTREE_NOTREF,
+         N_("@p @h %d: node (%B) not referenced\n"),
+         PROMPT_NONE, 0 },
+
+       /* Node in HTREE directory referenced twice */
+       { PR_2_HTREE_DUPREF,
+         N_("@p @h %d: node (%B) referenced twice\n"),
+         PROMPT_NONE, 0 },
+
+       /* Node in HTREE directory has bad min hash */
+       { PR_2_HTREE_MIN_HASH,
+         N_("@p @h %d: node (%B) has bad min hash\n"),
+         PROMPT_NONE, 0 },
+
+       /* Node in HTREE directory has bad max hash */
+       { PR_2_HTREE_MAX_HASH,
+         N_("@p @h %d: node (%B) has bad max hash\n"),
+         PROMPT_NONE, 0 },
+
+       /* Clear invalid HTREE directory */
+       { PR_2_HTREE_CLEAR,
+         N_("@n @h %d (%q).  "), PROMPT_CLEAR, 0 },
+
+       /* Bad block in htree interior node */
+       { PR_2_HTREE_BADBLK,
+         N_("@p @h %d (%q): bad @b number %b.\n"),
+         PROMPT_CLEAR_HTREE, 0 },
+
+       /* Error adjusting EA refcount */
+       { PR_2_ADJ_EA_REFCOUNT,
+         N_("Error adjusting refcount for @a @b %b (@i %i): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Invalid HTREE root node */
+       { PR_2_HTREE_BAD_ROOT,
+         N_("@p @h %d: root node is @n\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* Invalid HTREE limit */
+       { PR_2_HTREE_BAD_LIMIT,
+         N_("@p @h %d: node (%B) has @n limit (%N)\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* Invalid HTREE count */
+       { PR_2_HTREE_BAD_COUNT,
+         N_("@p @h %d: node (%B) has @n count (%N)\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* HTREE interior node has out-of-order hashes in table */
+       { PR_2_HTREE_HASH_ORDER,
+         N_("@p @h %d: node (%B) has an unordered hash table\n"),
+         PROMPT_CLEAR_HTREE, PR_PREEN_OK },
+
+       /* Node in HTREE directory has invalid depth */
+       { PR_2_HTREE_BAD_DEPTH,
+         N_("@p @h %d: node (%B) has @n depth\n"),
+         PROMPT_NONE, 0 },
+
+       /* Duplicate directory entry found */
+       { PR_2_DUPLICATE_DIRENT,
+         N_("Duplicate @E found.  "),
+         PROMPT_CLEAR, 0 },
+
+       /* Non-unique filename found */
+       { PR_2_NON_UNIQUE_FILE, /* xgettext: no-c-format */
+         N_("@E has a non-unique filename.\nRename to %s"),
+         PROMPT_NULL, 0 },
+
+       /* Duplicate directory entry found */
+       { PR_2_REPORT_DUP_DIRENT,
+         N_("Duplicate @e '%Dn' found.\n\tMarking %p (%i) to be rebuilt.\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Pass 3 errors */
+
+       /* Pass 3: Checking directory connectivity */
+       { PR_3_PASS_HEADER,
+         N_("Pass 3: Checking @d connectivity\n"),
+         PROMPT_NONE, 0 },
+
+       /* Root inode not allocated */
+       { PR_3_NO_ROOT_INODE,
+         N_("@r not allocated.  "),
+         PROMPT_ALLOCATE, 0 },
+
+       /* No room in lost+found */
+       { PR_3_EXPAND_LF_DIR,
+         N_("No room in @l @d.  "),
+         PROMPT_EXPAND, 0 },
+
+       /* Unconnected directory inode */
+       { PR_3_UNCONNECTED_DIR,
+         N_("Unconnected @d @i %i (%p)\n"),
+         PROMPT_CONNECT, 0 },
+
+       /* /lost+found not found */
+       { PR_3_NO_LF_DIR,
+         N_("/@l not found.  "),
+         PROMPT_CREATE, PR_PREEN_OK },
+
+       /* .. entry is incorrect */
+       { PR_3_BAD_DOT_DOT,
+         N_("'..' in %Q (%i) is %P (%j), @s %q (%d).\n"),
+         PROMPT_FIX, 0 },
+
+       /* Bad or non-existent /lost+found.  Cannot reconnect */
+       { PR_3_NO_LPF,
+         N_("Bad or non-existent /@l.  Cannot reconnect.\n"),
+         PROMPT_NONE, 0 },
+
+       /* Could not expand /lost+found */
+       { PR_3_CANT_EXPAND_LPF,
+         N_("Could not expand /@l: %m\n"),
+         PROMPT_NONE, 0 },
+
+       /* Could not reconnect inode */
+       { PR_3_CANT_RECONNECT,
+         N_("Could not reconnect %i: %m\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error while trying to find /lost+found */
+       { PR_3_ERR_FIND_LPF,
+         N_("Error while trying to find /@l: %m\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error in ext2fs_new_block while creating /lost+found */
+       { PR_3_ERR_LPF_NEW_BLOCK,
+         N_("ext2fs_new_@b: %m while trying to create /@l @d\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error in ext2fs_new_inode while creating /lost+found */
+       { PR_3_ERR_LPF_NEW_INODE,
+         N_("ext2fs_new_@i: %m while trying to create /@l @d\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error in ext2fs_new_dir_block while creating /lost+found */
+       { PR_3_ERR_LPF_NEW_DIR_BLOCK,
+         N_("ext2fs_new_dir_@b: %m while creating new @d @b\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error while writing directory block for /lost+found */
+       { PR_3_ERR_LPF_WRITE_BLOCK,
+         N_("ext2fs_write_dir_@b: %m while writing the @d @b for /@l\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error while adjusting inode count */
+       { PR_3_ADJUST_INODE,
+         N_("Error while adjusting @i count on @i %i\n"),
+         PROMPT_NONE, 0 },
+
+       /* Couldn't fix parent directory -- error */
+       { PR_3_FIX_PARENT_ERR,
+         N_("Couldn't fix parent of @i %i: %m\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Couldn't fix parent directory -- couldn't find it */
+       { PR_3_FIX_PARENT_NOFIND,
+         N_("Couldn't fix parent of @i %i: Couldn't find parent @d @e\n\n"),
+         PROMPT_NONE, 0 },
+
+       /* Error allocating inode bitmap */
+       { PR_3_ALLOCATE_IBITMAP_ERROR,
+         N_("@A @i @B (%N): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error creating root directory */
+       { PR_3_CREATE_ROOT_ERROR,
+         N_("Error creating root @d (%s): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error creating lost and found directory */
+       { PR_3_CREATE_LPF_ERROR,
+         N_("Error creating /@l @d (%s): %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Root inode is not directory; aborting */
+       { PR_3_ROOT_NOT_DIR_ABORT,
+         N_("@r is not a @d; aborting.\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Cannot proceed without a root inode. */
+       { PR_3_NO_ROOT_INODE_ABORT,
+         N_("Cannot proceed without a @r.\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Internal error: couldn't find dir_info */
+       { PR_3_NO_DIRINFO,
+         N_("Internal error: cannot find dir_info for %i.\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Lost+found not a directory */
+       { PR_3_LPF_NOTDIR,
+         N_("/@l is not a @d (ino=%i)\n"),
+         PROMPT_UNLINK, 0 },
+
+       /* Pass 3A Directory Optimization       */
+
+       /* Pass 3A: Optimizing directories */
+       { PR_3A_PASS_HEADER,
+         N_("Pass 3A: Optimizing directories\n"),
+         PROMPT_NONE, PR_PREEN_NOMSG },
+
+       /* Error iterating over directories */
+       { PR_3A_OPTIMIZE_ITER,
+         N_("Failed to create dirs_to_hash iterator: %m"),
+         PROMPT_NONE, 0 },
+
+       /* Error rehash directory */
+       { PR_3A_OPTIMIZE_DIR_ERR,
+         N_("Failed to optimize directory %q (%d): %m"),
+         PROMPT_NONE, 0 },
+
+       /* Rehashing dir header */
+       { PR_3A_OPTIMIZE_DIR_HEADER,
+         N_("Optimizing directories: "),
+         PROMPT_NONE, PR_MSG_ONLY },
+
+       /* Rehashing directory %d */
+       { PR_3A_OPTIMIZE_DIR,
+         " %d",
+         PROMPT_NONE, PR_LATCH_OPTIMIZE_DIR | PR_PREEN_NOHDR},
+
+       /* Rehashing dir end */
+       { PR_3A_OPTIMIZE_DIR_END,
+         "\n",
+         PROMPT_NONE, PR_PREEN_NOHDR },
+
+       /* Pass 4 errors */
+
+       /* Pass 4: Checking reference counts */
+       { PR_4_PASS_HEADER,
+         N_("Pass 4: Checking reference counts\n"),
+         PROMPT_NONE, 0 },
+
+       /* Unattached zero-length inode */
+       { PR_4_ZERO_LEN_INODE,
+         N_("@u @z @i %i.  "),
+         PROMPT_CLEAR, PR_PREEN_OK|PR_NO_OK },
+
+       /* Unattached inode */
+       { PR_4_UNATTACHED_INODE,
+         N_("@u @i %i\n"),
+         PROMPT_CONNECT, 0 },
+
+       /* Inode ref count wrong */
+       { PR_4_BAD_REF_COUNT,
+         N_("@i %i ref count is %Il, @s %N.  "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       { PR_4_INCONSISTENT_COUNT,
+         N_("WARNING: PROGRAMMING BUG IN E2FSCK!\n"
+         "\tOR SOME BONEHEAD (YOU) IS CHECKING A MOUNTED (LIVE) FILESYSTEM.\n"
+         "@i_link_info[%i] is %N, @i.i_links_count is %Il.  "
+         "They @s the same!\n"),
+         PROMPT_NONE, 0 },
+
+       /* Pass 5 errors */
+
+       /* Pass 5: Checking group summary information */
+       { PR_5_PASS_HEADER,
+         N_("Pass 5: Checking @g summary information\n"),
+         PROMPT_NONE, 0 },
+
+       /* Padding at end of inode bitmap is not set. */
+       { PR_5_INODE_BMAP_PADDING,
+         N_("Padding at end of @i @B is not set. "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Padding at end of block bitmap is not set. */
+       { PR_5_BLOCK_BMAP_PADDING,
+         N_("Padding at end of @b @B is not set. "),
+         PROMPT_FIX, PR_PREEN_OK },
+
+       /* Block bitmap differences header */
+       { PR_5_BLOCK_BITMAP_HEADER,
+         N_("@b @B differences: "),
+         PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG},
+
+       /* Block not used, but marked in bitmap */
+       { PR_5_BLOCK_UNUSED,
+         " -%b",
+         PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Block used, but not marked used in bitmap */
+       { PR_5_BLOCK_USED,
+         " +%b",
+         PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Block bitmap differences end */
+       { PR_5_BLOCK_BITMAP_END,
+         "\n",
+         PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Inode bitmap differences header */
+       { PR_5_INODE_BITMAP_HEADER,
+         N_("@i @B differences: "),
+         PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Inode not used, but marked in bitmap */
+       { PR_5_INODE_UNUSED,
+         " -%i",
+         PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Inode used, but not marked used in bitmap */
+       { PR_5_INODE_USED,
+         " +%i",
+         PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Inode bitmap differences end */
+       { PR_5_INODE_BITMAP_END,
+         "\n",
+         PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Free inodes count for group wrong */
+       { PR_5_FREE_INODE_COUNT_GROUP,
+         N_("Free @is count wrong for @g #%g (%i, counted=%j).\n"),
+         PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Directories count for group wrong */
+       { PR_5_FREE_DIR_COUNT_GROUP,
+         N_("Directories count wrong for @g #%g (%i, counted=%j).\n"),
+         PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Free inodes count wrong */
+       { PR_5_FREE_INODE_COUNT,
+         N_("Free @is count wrong (%i, counted=%j).\n"),
+         PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Free blocks count for group wrong */
+       { PR_5_FREE_BLOCK_COUNT_GROUP,
+         N_("Free @bs count wrong for @g #%g (%b, counted=%c).\n"),
+         PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Free blocks count wrong */
+       { PR_5_FREE_BLOCK_COUNT,
+         N_("Free @bs count wrong (%b, counted=%c).\n"),
+         PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Programming error: bitmap endpoints don't match */
+       { PR_5_BMAP_ENDPOINTS,
+         N_("PROGRAMMING ERROR: @f (#%N) @B endpoints (%b, %c) don't "
+         "match calculated @B endpoints (%i, %j)\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Internal error: fudging end of bitmap */
+       { PR_5_FUDGE_BITMAP_ERROR,
+         N_("Internal error: fudging end of bitmap (%N)\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error copying in replacement inode bitmap */
+       { PR_5_COPY_IBITMAP_ERROR,
+         N_("Error copying in replacement @i @B: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Error copying in replacement block bitmap */
+       { PR_5_COPY_BBITMAP_ERROR,
+         N_("Error copying in replacement @b @B: %m\n"),
+         PROMPT_NONE, PR_FATAL },
+
+       /* Block range not used, but marked in bitmap */
+       { PR_5_BLOCK_RANGE_UNUSED,
+         " -(%b--%c)",
+         PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Block range used, but not marked used in bitmap */
+       { PR_5_BLOCK_RANGE_USED,
+         " +(%b--%c)",
+         PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Inode range not used, but marked in bitmap */
+       { PR_5_INODE_RANGE_UNUSED,
+         " -(%i--%j)",
+         PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       /* Inode range used, but not marked used in bitmap */
+       { PR_5_INODE_RANGE_USED,
+         " +(%i--%j)",
+         PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
+
+       { 0 }
+};
+
+/*
+ * This is the latch flags register.  It allows several problems to be
+ * "latched" together.  This means that the user has to answer but one
+ * question for the set of problems, and all of the associated
+ * problems will be either fixed or not fixed.
+ */
+static struct latch_descr pr_latch_info[] = {
+       { PR_LATCH_BLOCK, PR_1_INODE_BLOCK_LATCH, 0 },
+       { PR_LATCH_BBLOCK, PR_1_INODE_BBLOCK_LATCH, 0 },
+       { PR_LATCH_IBITMAP, PR_5_INODE_BITMAP_HEADER, PR_5_INODE_BITMAP_END },
+       { PR_LATCH_BBITMAP, PR_5_BLOCK_BITMAP_HEADER, PR_5_BLOCK_BITMAP_END },
+       { PR_LATCH_RELOC, PR_0_RELOCATE_HINT, 0 },
+       { PR_LATCH_DBLOCK, PR_1B_DUP_BLOCK_HEADER, PR_1B_DUP_BLOCK_END },
+       { PR_LATCH_LOW_DTIME, PR_1_ORPHAN_LIST_REFUGEES, 0 },
+       { PR_LATCH_TOOBIG, PR_1_INODE_TOOBIG, 0 },
+       { PR_LATCH_OPTIMIZE_DIR, PR_3A_OPTIMIZE_DIR_HEADER, PR_3A_OPTIMIZE_DIR_END },
+       { -1, 0, 0 },
+};
+
+static const struct e2fsck_problem *find_problem(problem_t code)
+{
+       int     i;
+
+       for (i=0; problem_table[i].e2p_code; i++) {
+               if (problem_table[i].e2p_code == code)
+                       return &problem_table[i];
+       }
+       return 0;
+}
+
+static struct latch_descr *find_latch(int code)
+{
+       int     i;
+
+       for (i=0; pr_latch_info[i].latch_code >= 0; i++) {
+               if (pr_latch_info[i].latch_code == code)
+                       return &pr_latch_info[i];
+       }
+       return 0;
+}
+
+int end_problem_latch(e2fsck_t ctx, int mask)
+{
+       struct latch_descr *ldesc;
+       struct problem_context pctx;
+       int answer = -1;
+
+       ldesc = find_latch(mask);
+       if (ldesc->end_message && (ldesc->flags & PRL_LATCHED)) {
+               clear_problem_context(&pctx);
+               answer = fix_problem(ctx, ldesc->end_message, &pctx);
+       }
+       ldesc->flags &= ~(PRL_VARIABLE);
+       return answer;
+}
+
+int set_latch_flags(int mask, int setflags, int clearflags)
+{
+       struct latch_descr *ldesc;
+
+       ldesc = find_latch(mask);
+       if (!ldesc)
+               return -1;
+       ldesc->flags |= setflags;
+       ldesc->flags &= ~clearflags;
+       return 0;
+}
+
+void clear_problem_context(struct problem_context *ctx)
+{
+       memset(ctx, 0, sizeof(struct problem_context));
+       ctx->blkcount = -1;
+       ctx->group = -1;
+}
+
+int fix_problem(e2fsck_t ctx, problem_t code, struct problem_context *pctx)
+{
+       ext2_filsys fs = ctx->fs;
+       const struct e2fsck_problem *ptr;
+       struct latch_descr *ldesc = 0;
+       const char *message;
+       int             def_yn, answer, ans;
+       int             print_answer = 0;
+       int             suppress = 0;
+
+       ptr = find_problem(code);
+       if (!ptr) {
+               printf(_("Unhandled error code (0x%x)!\n"), code);
+               return 0;
+       }
+       def_yn = 1;
+       if ((ptr->flags & PR_NO_DEFAULT) ||
+           ((ptr->flags & PR_PREEN_NO) && (ctx->options & E2F_OPT_PREEN)) ||
+           (ctx->options & E2F_OPT_NO))
+               def_yn= 0;
+
+       /*
+        * Do special latch processing.  This is where we ask the
+        * latch question, if it exists
+        */
+       if (ptr->flags & PR_LATCH_MASK) {
+               ldesc = find_latch(ptr->flags & PR_LATCH_MASK);
+               if (ldesc->question && !(ldesc->flags & PRL_LATCHED)) {
+                       ans = fix_problem(ctx, ldesc->question, pctx);
+                       if (ans == 1)
+                               ldesc->flags |= PRL_YES;
+                       if (ans == 0)
+                               ldesc->flags |= PRL_NO;
+                       ldesc->flags |= PRL_LATCHED;
+               }
+               if (ldesc->flags & PRL_SUPPRESS)
+                       suppress++;
+       }
+       if ((ptr->flags & PR_PREEN_NOMSG) &&
+           (ctx->options & E2F_OPT_PREEN))
+               suppress++;
+       if ((ptr->flags & PR_NO_NOMSG) &&
+           (ctx->options & E2F_OPT_NO))
+               suppress++;
+       if (!suppress) {
+               message = ptr->e2p_description;
+               if ((ctx->options & E2F_OPT_PREEN) &&
+                   !(ptr->flags & PR_PREEN_NOHDR)) {
+                       printf("%s: ", ctx->device_name ?
+                              ctx->device_name : ctx->filesystem_name);
+               }
+               if (*message)
+                       print_e2fsck_message(ctx, _(message), pctx, 1);
+       }
+       if (!(ptr->flags & PR_PREEN_OK) && (ptr->prompt != PROMPT_NONE))
+               preenhalt(ctx);
+
+       if (ptr->flags & PR_FATAL)
+               bb_error_msg_and_die(0);
+
+       if (ptr->prompt == PROMPT_NONE) {
+               if (ptr->flags & PR_NOCOLLATE)
+                       answer = -1;
+               else
+                       answer = def_yn;
+       } else {
+               if (ctx->options & E2F_OPT_PREEN) {
+                       answer = def_yn;
+                       if (!(ptr->flags & PR_PREEN_NOMSG))
+                               print_answer = 1;
+               } else if ((ptr->flags & PR_LATCH_MASK) &&
+                          (ldesc->flags & (PRL_YES | PRL_NO))) {
+                       if (!suppress)
+                               print_answer = 1;
+                       if (ldesc->flags & PRL_YES)
+                               answer = 1;
+                       else
+                               answer = 0;
+               } else
+                       answer = ask(ctx, _(prompt[(int) ptr->prompt]), def_yn);
+               if (!answer && !(ptr->flags & PR_NO_OK))
+                       ext2fs_unmark_valid(fs);
+
+               if (print_answer)
+                       printf("%s.\n", answer ?
+                              _(preen_msg[(int) ptr->prompt]) : _("IGNORED"));
+
+       }
+
+       if ((ptr->prompt == PROMPT_ABORT) && answer)
+               bb_error_msg_and_die(0);
+
+       if (ptr->flags & PR_AFTER_CODE)
+               answer = fix_problem(ctx, ptr->second_code, pctx);
+
+       return answer;
+}
+
+/*
+ * linux/fs/recovery.c
+ *
+ * Written by Stephen C. Tweedie <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(0);
+               } else {
+                       inode.i_dtime = 0;
+               }
+               e2fsck_write_inode(ctx, ino, &inode, "delete_file");
+               ino = next_ino;
+       }
+       ext2fs_free_mem(&block_buf);
+       return 0;
+return_abort:
+       ext2fs_free_mem(&block_buf);
+       return 1;
+}
+
+/*
+ * Check the resize inode to make sure it is sane.  We check both for
+ * the case where on-line resizing is not enabled (in which case the
+ * resize inode should be cleared) as well as the case where on-line
+ * resizing is enabled.
+ */
+static void check_resize_inode(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       struct ext2_inode inode;
+       struct problem_context  pctx;
+       int             i, j, gdt_off, ind_off;
+       blk_t           blk, pblk, expect;
+       __u32           *dind_buf = 0, *ind_buf;
+       errcode_t       retval;
+
+       clear_problem_context(&pctx);
+
+       /*
+        * If the resize inode feature isn't set, then
+        * s_reserved_gdt_blocks must be zero.
+        */
+       if (!(fs->super->s_feature_compat &
+             EXT2_FEATURE_COMPAT_RESIZE_INODE)) {
+               if (fs->super->s_reserved_gdt_blocks) {
+                       pctx.num = fs->super->s_reserved_gdt_blocks;
+                       if (fix_problem(ctx, PR_0_NONZERO_RESERVED_GDT_BLOCKS,
+                                       &pctx)) {
+                               fs->super->s_reserved_gdt_blocks = 0;
+                               ext2fs_mark_super_dirty(fs);
+                       }
+               }
+       }
+
+       /* Read the resize inode */
+       pctx.ino = EXT2_RESIZE_INO;
+       retval = ext2fs_read_inode(fs, EXT2_RESIZE_INO, &inode);
+       if (retval) {
+               if (fs->super->s_feature_compat &
+                   EXT2_FEATURE_COMPAT_RESIZE_INODE)
+                       ctx->flags |= E2F_FLAG_RESIZE_INODE;
+               return;
+       }
+
+       /*
+        * If the resize inode feature isn't set, check to make sure
+        * the resize inode is cleared; then we're done.
+        */
+       if (!(fs->super->s_feature_compat &
+             EXT2_FEATURE_COMPAT_RESIZE_INODE)) {
+               for (i=0; i < EXT2_N_BLOCKS; i++) {
+                       if (inode.i_block[i])
+                               break;
+               }
+               if ((i < EXT2_N_BLOCKS) &&
+                   fix_problem(ctx, PR_0_CLEAR_RESIZE_INODE, &pctx)) {
+                       memset(&inode, 0, sizeof(inode));
+                       e2fsck_write_inode(ctx, EXT2_RESIZE_INO, &inode,
+                                          "clear_resize");
+               }
+               return;
+       }
+
+       /*
+        * The resize inode feature is enabled; check to make sure the
+        * only block in use is the double indirect block
+        */
+       blk = inode.i_block[EXT2_DIND_BLOCK];
+       for (i=0; i < EXT2_N_BLOCKS; i++) {
+               if (i != EXT2_DIND_BLOCK && inode.i_block[i])
+                       break;
+       }
+       if ((i < EXT2_N_BLOCKS) || !blk || !inode.i_links_count ||
+           !(inode.i_mode & LINUX_S_IFREG) ||
+           (blk < fs->super->s_first_data_block ||
+            blk >= fs->super->s_blocks_count)) {
+       resize_inode_invalid:
+               if (fix_problem(ctx, PR_0_RESIZE_INODE_INVALID, &pctx)) {
+                       memset(&inode, 0, sizeof(inode));
+                       e2fsck_write_inode(ctx, EXT2_RESIZE_INO, &inode,
+                                          "clear_resize");
+                       ctx->flags |= E2F_FLAG_RESIZE_INODE;
+               }
+               if (!(ctx->options & E2F_OPT_READONLY)) {
+                       fs->super->s_state &= ~EXT2_VALID_FS;
+                       ext2fs_mark_super_dirty(fs);
+               }
+               goto cleanup;
+       }
+       dind_buf = (__u32 *) e2fsck_allocate_memory(ctx, fs->blocksize * 2,
+                                                   "resize dind buffer");
+       ind_buf = (__u32 *) ((char *) dind_buf + fs->blocksize);
+
+       retval = ext2fs_read_ind_block(fs, blk, dind_buf);
+       if (retval)
+               goto resize_inode_invalid;
+
+       gdt_off = fs->desc_blocks;
+       pblk = fs->super->s_first_data_block + 1 + fs->desc_blocks;
+       for (i = 0; i < fs->super->s_reserved_gdt_blocks / 4;
+            i++, gdt_off++, pblk++) {
+               gdt_off %= fs->blocksize/4;
+               if (dind_buf[gdt_off] != pblk)
+                       goto resize_inode_invalid;
+               retval = ext2fs_read_ind_block(fs, pblk, ind_buf);
+               if (retval)
+                       goto resize_inode_invalid;
+               ind_off = 0;
+               for (j = 1; j < fs->group_desc_count; j++) {
+                       if (!ext2fs_bg_has_super(fs, j))
+                               continue;
+                       expect = pblk + (j * fs->super->s_blocks_per_group);
+                       if (ind_buf[ind_off] != expect)
+                               goto resize_inode_invalid;
+                       ind_off++;
+               }
+       }
+
+cleanup:
+       ext2fs_free_mem(&dind_buf);
+
+ }
+
+static void check_super_block(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       blk_t   first_block, last_block;
+       struct ext2_super_block *sb = fs->super;
+       struct ext2_group_desc *gd;
+       blk_t   blocks_per_group = fs->super->s_blocks_per_group;
+       blk_t   bpg_max;
+       int     inodes_per_block;
+       int     ipg_max;
+       int     inode_size;
+       dgrp_t  i;
+       blk_t   should_be;
+       struct problem_context  pctx;
+       __u32   free_blocks = 0, free_inodes = 0;
+
+       inodes_per_block = EXT2_INODES_PER_BLOCK(fs->super);
+       ipg_max = inodes_per_block * (blocks_per_group - 4);
+       if (ipg_max > EXT2_MAX_INODES_PER_GROUP(sb))
+               ipg_max = EXT2_MAX_INODES_PER_GROUP(sb);
+       bpg_max = 8 * EXT2_BLOCK_SIZE(sb);
+       if (bpg_max > EXT2_MAX_BLOCKS_PER_GROUP(sb))
+               bpg_max = EXT2_MAX_BLOCKS_PER_GROUP(sb);
+
+       ctx->invalid_inode_bitmap_flag = (int *) e2fsck_allocate_memory(ctx,
+                sizeof(int) * fs->group_desc_count, "invalid_inode_bitmap");
+       ctx->invalid_block_bitmap_flag = (int *) e2fsck_allocate_memory(ctx,
+                sizeof(int) * fs->group_desc_count, "invalid_block_bitmap");
+       ctx->invalid_inode_table_flag = (int *) e2fsck_allocate_memory(ctx,
+               sizeof(int) * fs->group_desc_count, "invalid_inode_table");
+
+       clear_problem_context(&pctx);
+
+       /*
+        * Verify the super block constants...
+        */
+       check_super_value(ctx, "inodes_count", sb->s_inodes_count,
+                         MIN_CHECK, 1, 0);
+       check_super_value(ctx, "blocks_count", sb->s_blocks_count,
+                         MIN_CHECK, 1, 0);
+       check_super_value(ctx, "first_data_block", sb->s_first_data_block,
+                         MAX_CHECK, 0, sb->s_blocks_count);
+       check_super_value(ctx, "log_block_size", sb->s_log_block_size,
+                         MIN_CHECK | MAX_CHECK, 0,
+                         EXT2_MAX_BLOCK_LOG_SIZE - EXT2_MIN_BLOCK_LOG_SIZE);
+       check_super_value(ctx, "log_frag_size", sb->s_log_frag_size,
+                         MIN_CHECK | MAX_CHECK, 0, sb->s_log_block_size);
+       check_super_value(ctx, "frags_per_group", sb->s_frags_per_group,
+                         MIN_CHECK | MAX_CHECK, sb->s_blocks_per_group,
+                         bpg_max);
+       check_super_value(ctx, "blocks_per_group", sb->s_blocks_per_group,
+                         MIN_CHECK | MAX_CHECK, 8, bpg_max);
+       check_super_value(ctx, "inodes_per_group", sb->s_inodes_per_group,
+                         MIN_CHECK | MAX_CHECK, inodes_per_block, ipg_max);
+       check_super_value(ctx, "r_blocks_count", sb->s_r_blocks_count,
+                         MAX_CHECK, 0, sb->s_blocks_count / 2);
+       check_super_value(ctx, "reserved_gdt_blocks",
+                         sb->s_reserved_gdt_blocks, MAX_CHECK, 0,
+                         fs->blocksize/4);
+       inode_size = EXT2_INODE_SIZE(sb);
+       check_super_value(ctx, "inode_size",
+                         inode_size, MIN_CHECK | MAX_CHECK,
+                         EXT2_GOOD_OLD_INODE_SIZE, fs->blocksize);
+       if (inode_size & (inode_size - 1)) {
+               pctx.num = inode_size;
+               pctx.str = "inode_size";
+               fix_problem(ctx, PR_0_MISC_CORRUPT_SUPER, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT; /* never get here! */
+               return;
+       }
+
+       if (!ctx->num_blocks) {
+               pctx.errcode = e2fsck_get_device_size(ctx);
+               if (pctx.errcode && pctx.errcode != EXT2_ET_UNIMPLEMENTED) {
+                       fix_problem(ctx, PR_0_GETSIZE_ERROR, &pctx);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+               if ((pctx.errcode != EXT2_ET_UNIMPLEMENTED) &&
+                   (ctx->num_blocks < sb->s_blocks_count)) {
+                       pctx.blk = sb->s_blocks_count;
+                       pctx.blk2 = ctx->num_blocks;
+                       if (fix_problem(ctx, PR_0_FS_SIZE_WRONG, &pctx)) {
+                               ctx->flags |= E2F_FLAG_ABORT;
+                               return;
+                       }
+               }
+       }
+
+       if (sb->s_log_block_size != (__u32) sb->s_log_frag_size) {
+               pctx.blk = EXT2_BLOCK_SIZE(sb);
+               pctx.blk2 = EXT2_FRAG_SIZE(sb);
+               fix_problem(ctx, PR_0_NO_FRAGMENTS, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+       should_be = sb->s_frags_per_group >>
+               (sb->s_log_block_size - sb->s_log_frag_size);
+       if (sb->s_blocks_per_group != should_be) {
+               pctx.blk = sb->s_blocks_per_group;
+               pctx.blk2 = should_be;
+               fix_problem(ctx, PR_0_BLOCKS_PER_GROUP, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+       should_be = (sb->s_log_block_size == 0) ? 1 : 0;
+       if (sb->s_first_data_block != should_be) {
+               pctx.blk = sb->s_first_data_block;
+               pctx.blk2 = should_be;
+               fix_problem(ctx, PR_0_FIRST_DATA_BLOCK, &pctx);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+
+       should_be = sb->s_inodes_per_group * fs->group_desc_count;
+       if (sb->s_inodes_count != should_be) {
+               pctx.ino = sb->s_inodes_count;
+               pctx.ino2 = should_be;
+               if (fix_problem(ctx, PR_0_INODE_COUNT_WRONG, &pctx)) {
+                       sb->s_inodes_count = should_be;
+                       ext2fs_mark_super_dirty(fs);
+               }
+       }
+
+       /*
+        * Verify the group descriptors....
+        */
+       first_block =  sb->s_first_data_block;
+       last_block = first_block + blocks_per_group;
+
+       for (i = 0, gd=fs->group_desc; i < fs->group_desc_count; i++, gd++) {
+               pctx.group = i;
+
+               if (i == fs->group_desc_count - 1)
+                       last_block = sb->s_blocks_count;
+               if ((gd->bg_block_bitmap < first_block) ||
+                   (gd->bg_block_bitmap >= last_block)) {
+                       pctx.blk = gd->bg_block_bitmap;
+                       if (fix_problem(ctx, PR_0_BB_NOT_GROUP, &pctx))
+                               gd->bg_block_bitmap = 0;
+               }
+               if (gd->bg_block_bitmap == 0) {
+                       ctx->invalid_block_bitmap_flag[i]++;
+                       ctx->invalid_bitmaps++;
+               }
+               if ((gd->bg_inode_bitmap < first_block) ||
+                   (gd->bg_inode_bitmap >= last_block)) {
+                       pctx.blk = gd->bg_inode_bitmap;
+                       if (fix_problem(ctx, PR_0_IB_NOT_GROUP, &pctx))
+                               gd->bg_inode_bitmap = 0;
+               }
+               if (gd->bg_inode_bitmap == 0) {
+                       ctx->invalid_inode_bitmap_flag[i]++;
+                       ctx->invalid_bitmaps++;
+               }
+               if ((gd->bg_inode_table < first_block) ||
+                   ((gd->bg_inode_table +
+                     fs->inode_blocks_per_group - 1) >= last_block)) {
+                       pctx.blk = gd->bg_inode_table;
+                       if (fix_problem(ctx, PR_0_ITABLE_NOT_GROUP, &pctx))
+                               gd->bg_inode_table = 0;
+               }
+               if (gd->bg_inode_table == 0) {
+                       ctx->invalid_inode_table_flag[i]++;
+                       ctx->invalid_bitmaps++;
+               }
+               free_blocks += gd->bg_free_blocks_count;
+               free_inodes += gd->bg_free_inodes_count;
+               first_block += sb->s_blocks_per_group;
+               last_block += sb->s_blocks_per_group;
+
+               if ((gd->bg_free_blocks_count > sb->s_blocks_per_group) ||
+                   (gd->bg_free_inodes_count > sb->s_inodes_per_group) ||
+                   (gd->bg_used_dirs_count > sb->s_inodes_per_group))
+                       ext2fs_unmark_valid(fs);
+
+       }
+
+       /*
+        * Update the global counts from the block group counts.  This
+        * is needed for an experimental patch which eliminates
+        * locking the entire filesystem when allocating blocks or
+        * inodes; if the filesystem is not unmounted cleanly, the
+        * global counts may not be accurate.
+        */
+       if ((free_blocks != sb->s_free_blocks_count) ||
+           (free_inodes != sb->s_free_inodes_count)) {
+               if (ctx->options & E2F_OPT_READONLY)
+                       ext2fs_unmark_valid(fs);
+               else {
+                       sb->s_free_blocks_count = free_blocks;
+                       sb->s_free_inodes_count = free_inodes;
+                       ext2fs_mark_super_dirty(fs);
+               }
+       }
+
+       if ((sb->s_free_blocks_count > sb->s_blocks_count) ||
+           (sb->s_free_inodes_count > sb->s_inodes_count))
+               ext2fs_unmark_valid(fs);
+
+
+       /*
+        * If we have invalid bitmaps, set the error state of the
+        * filesystem.
+        */
+       if (ctx->invalid_bitmaps && !(ctx->options & E2F_OPT_READONLY)) {
+               sb->s_state &= ~EXT2_VALID_FS;
+               ext2fs_mark_super_dirty(fs);
+       }
+
+       clear_problem_context(&pctx);
+
+       /*
+        * If the UUID field isn't assigned, assign it.
+        */
+       if (!(ctx->options & E2F_OPT_READONLY) && uuid_is_null(sb->s_uuid)) {
+               if (fix_problem(ctx, PR_0_ADD_UUID, &pctx)) {
+                       uuid_generate(sb->s_uuid);
+                       ext2fs_mark_super_dirty(fs);
+                       fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY;
+               }
+       }
+
+       /* FIXME - HURD support?
+        * For the Hurd, check to see if the filetype option is set,
+        * since it doesn't support it.
+        */
+       if (!(ctx->options & E2F_OPT_READONLY) &&
+           fs->super->s_creator_os == EXT2_OS_HURD &&
+           (fs->super->s_feature_incompat &
+            EXT2_FEATURE_INCOMPAT_FILETYPE)) {
+               if (fix_problem(ctx, PR_0_HURD_CLEAR_FILETYPE, &pctx)) {
+                       fs->super->s_feature_incompat &=
+                               ~EXT2_FEATURE_INCOMPAT_FILETYPE;
+                       ext2fs_mark_super_dirty(fs);
+
+               }
+       }
+
+       /*
+        * If we have any of the compatibility flags set, we need to have a
+        * revision 1 filesystem.  Most kernels will not check the flags on
+        * a rev 0 filesystem and we may have corruption issues because of
+        * the incompatible changes to the filesystem.
+        */
+       if (!(ctx->options & E2F_OPT_READONLY) &&
+           fs->super->s_rev_level == EXT2_GOOD_OLD_REV &&
+           (fs->super->s_feature_compat ||
+            fs->super->s_feature_ro_compat ||
+            fs->super->s_feature_incompat) &&
+           fix_problem(ctx, PR_0_FS_REV_LEVEL, &pctx)) {
+               ext2fs_update_dynamic_rev(fs);
+               ext2fs_mark_super_dirty(fs);
+       }
+
+       check_resize_inode(ctx);
+
+       /*
+        * Clean up any orphan inodes, if present.
+        */
+       if (!(ctx->options & E2F_OPT_READONLY) && release_orphan_inodes(ctx)) {
+               fs->super->s_state &= ~EXT2_VALID_FS;
+               ext2fs_mark_super_dirty(fs);
+       }
+
+       /*
+        * Move the ext3 journal file, if necessary.
+        */
+       e2fsck_move_ext3_journal(ctx);
+}
+
+/*
+ * swapfs.c --- byte-swap an ext2 filesystem
+ */
+
+#ifdef ENABLE_SWAPFS
+
+struct swap_block_struct {
+       ext2_ino_t      ino;
+       int             isdir;
+       errcode_t       errcode;
+       char            *dir_buf;
+       struct ext2_inode *inode;
+};
+
+/*
+ * This is a helper function for block_iterate.  We mark all of the
+ * indirect and direct blocks as changed, so that block_iterate will
+ * write them out.
+ */
+static int swap_block(ext2_filsys fs, blk_t *block_nr, int blockcnt,
+                     void *priv_data)
+{
+       errcode_t       retval;
+
+       struct swap_block_struct *sb = (struct swap_block_struct *) priv_data;
+
+       if (sb->isdir && (blockcnt >= 0) && *block_nr) {
+               retval = ext2fs_read_dir_block(fs, *block_nr, sb->dir_buf);
+               if (retval) {
+                       sb->errcode = retval;
+                       return BLOCK_ABORT;
+               }
+               retval = ext2fs_write_dir_block(fs, *block_nr, sb->dir_buf);
+               if (retval) {
+                       sb->errcode = retval;
+                       return BLOCK_ABORT;
+               }
+       }
+       if (blockcnt >= 0) {
+               if (blockcnt < EXT2_NDIR_BLOCKS)
+                       return 0;
+               return BLOCK_CHANGED;
+       }
+       if (blockcnt == BLOCK_COUNT_IND) {
+               if (*block_nr == sb->inode->i_block[EXT2_IND_BLOCK])
+                       return 0;
+               return BLOCK_CHANGED;
+       }
+       if (blockcnt == BLOCK_COUNT_DIND) {
+               if (*block_nr == sb->inode->i_block[EXT2_DIND_BLOCK])
+                       return 0;
+               return BLOCK_CHANGED;
+       }
+       if (blockcnt == BLOCK_COUNT_TIND) {
+               if (*block_nr == sb->inode->i_block[EXT2_TIND_BLOCK])
+                       return 0;
+               return BLOCK_CHANGED;
+       }
+       return BLOCK_CHANGED;
+}
+
+/*
+ * This function is responsible for byte-swapping all of the indirect,
+ * block pointers.  It is also responsible for byte-swapping directories.
+ */
+static void swap_inode_blocks(e2fsck_t ctx, ext2_ino_t ino, char *block_buf,
+                             struct ext2_inode *inode)
+{
+       errcode_t                       retval;
+       struct swap_block_struct        sb;
+
+       sb.ino = ino;
+       sb.inode = inode;
+       sb.dir_buf = block_buf + ctx->fs->blocksize*3;
+       sb.errcode = 0;
+       sb.isdir = 0;
+       if (LINUX_S_ISDIR(inode->i_mode))
+               sb.isdir = 1;
+
+       retval = ext2fs_block_iterate(ctx->fs, ino, 0, block_buf,
+                                     swap_block, &sb);
+       if (retval) {
+               bb_error_msg(_("while calling ext2fs_block_iterate"));
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+       if (sb.errcode) {
+               bb_error_msg(_("while calling iterator function"));
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+}
+
+static void swap_inodes(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       dgrp_t                  group;
+       unsigned int            i;
+       ext2_ino_t              ino = 1;
+       char                    *buf, *block_buf;
+       errcode_t               retval;
+       struct ext2_inode *     inode;
+
+       e2fsck_use_inode_shortcuts(ctx, 1);
+
+       retval = ext2fs_get_mem(fs->blocksize * fs->inode_blocks_per_group,
+                               &buf);
+       if (retval) {
+               bb_error_msg(_("while allocating inode buffer"));
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+       block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 4,
+                                                   "block interate buffer");
+       for (group = 0; group < fs->group_desc_count; group++) {
+               retval = io_channel_read_blk(fs->io,
+                     fs->group_desc[group].bg_inode_table,
+                     fs->inode_blocks_per_group, buf);
+               if (retval) {
+                       bb_error_msg(_("while reading inode table (group %d)"),
+                               group);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+               inode = (struct ext2_inode *) buf;
+               for (i=0; i < fs->super->s_inodes_per_group;
+                    i++, ino++, inode++) {
+                       ctx->stashed_ino = ino;
+                       ctx->stashed_inode = inode;
+
+                       if (fs->flags & EXT2_FLAG_SWAP_BYTES_READ)
+                               ext2fs_swap_inode(fs, inode, inode, 0);
+
+                       /*
+                        * Skip deleted files.
+                        */
+                       if (inode->i_links_count == 0)
+                               continue;
+
+                       if (LINUX_S_ISDIR(inode->i_mode) ||
+                           ((inode->i_block[EXT2_IND_BLOCK] ||
+                             inode->i_block[EXT2_DIND_BLOCK] ||
+                             inode->i_block[EXT2_TIND_BLOCK]) &&
+                            ext2fs_inode_has_valid_blocks(inode)))
+                               swap_inode_blocks(ctx, ino, block_buf, inode);
+
+                       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+                               return;
+
+                       if (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)
+                               ext2fs_swap_inode(fs, inode, inode, 1);
+               }
+               retval = io_channel_write_blk(fs->io,
+                     fs->group_desc[group].bg_inode_table,
+                     fs->inode_blocks_per_group, buf);
+               if (retval) {
+                       bb_error_msg(_("while writing inode table (group %d)"),
+                               group);
+                       ctx->flags |= E2F_FLAG_ABORT;
+                       return;
+               }
+       }
+       ext2fs_free_mem(&buf);
+       ext2fs_free_mem(&block_buf);
+       e2fsck_use_inode_shortcuts(ctx, 0);
+       ext2fs_flush_icache(fs);
+}
+
+#if defined(__powerpc__) && BB_BIG_ENDIAN
+/*
+ * On the PowerPC, the big-endian variant of the ext2 filesystem
+ * has its bitmaps stored as 32-bit words with bit 0 as the LSB
+ * of each word.  Thus a bitmap with only bit 0 set would be, as
+ * a string of bytes, 00 00 00 01 00 ...
+ * To cope with this, we byte-reverse each word of a bitmap if
+ * we have a big-endian filesystem, that is, if we are *not*
+ * byte-swapping other word-sized numbers.
+ */
+#define EXT2_BIG_ENDIAN_BITMAPS
+#endif
+
+#ifdef EXT2_BIG_ENDIAN_BITMAPS
+static void ext2fs_swap_bitmap(ext2fs_generic_bitmap bmap)
+{
+       __u32 *p = (__u32 *) bmap->bitmap;
+       int n, nbytes = (bmap->end - bmap->start + 7) / 8;
+
+       for (n = nbytes / sizeof(__u32); n > 0; --n, ++p)
+               *p = ext2fs_swab32(*p);
+}
+#endif
+
+
+#ifdef ENABLE_SWAPFS
+static void swap_filesys(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       if (!(ctx->options & E2F_OPT_PREEN))
+               printf(_("Pass 0: Doing byte-swap of filesystem\n"));
+
+       /* Byte swap */
+
+       if (fs->super->s_mnt_count) {
+               fprintf(stderr, _("%s: the filesystem must be freshly "
+                       "checked using fsck\n"
+                       "and not mounted before trying to "
+                       "byte-swap it.\n"), ctx->device_name);
+               ctx->flags |= E2F_FLAG_ABORT;
+               return;
+       }
+       if (fs->flags & EXT2_FLAG_SWAP_BYTES) {
+               fs->flags &= ~(EXT2_FLAG_SWAP_BYTES|
+                              EXT2_FLAG_SWAP_BYTES_WRITE);
+               fs->flags |= EXT2_FLAG_SWAP_BYTES_READ;
+       } else {
+               fs->flags &= ~EXT2_FLAG_SWAP_BYTES_READ;
+               fs->flags |= EXT2_FLAG_SWAP_BYTES_WRITE;
+       }
+       swap_inodes(ctx);
+       if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
+               return;
+       if (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)
+               fs->flags |= EXT2_FLAG_SWAP_BYTES;
+       fs->flags &= ~(EXT2_FLAG_SWAP_BYTES_READ|
+                      EXT2_FLAG_SWAP_BYTES_WRITE);
+
+#ifdef EXT2_BIG_ENDIAN_BITMAPS
+       e2fsck_read_bitmaps(ctx);
+       ext2fs_swap_bitmap(fs->inode_map);
+       ext2fs_swap_bitmap(fs->block_map);
+       fs->flags |= EXT2_FLAG_BB_DIRTY | EXT2_FLAG_IB_DIRTY;
+#endif
+       fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY;
+       ext2fs_flush(fs);
+       fs->flags |= EXT2_FLAG_MASTER_SB_ONLY;
+}
+#endif  /* ENABLE_SWAPFS */
+
+#endif
+
+/*
+ * util.c --- miscellaneous utilities
+ */
+
+
+void *e2fsck_allocate_memory(e2fsck_t ctx, unsigned int size,
+                            const char *description)
+{
+       void *ret;
+       char buf[256];
+
+       ret = malloc(size);
+       if (!ret) {
+               sprintf(buf, "Can't allocate %s\n", description);
+               bb_error_msg_and_die(buf);
+       }
+       memset(ret, 0, size);
+       return ret;
+}
+
+static char *string_copy(const char *str, int len)
+{
+       char    *ret;
+
+       if (!str)
+               return NULL;
+       if (!len)
+               len = strlen(str);
+       ret = malloc(len+1);
+       if (ret) {
+               strncpy(ret, str, len);
+               ret[len] = 0;
+       }
+       return ret;
+}
+
+#ifndef HAVE_CONIO_H
+static int read_a_char(void)
+{
+       char    c;
+       int     r;
+       int     fail = 0;
+
+       while(1) {
+               if (e2fsck_global_ctx &&
+                   (e2fsck_global_ctx->flags & E2F_FLAG_CANCEL)) {
+                       return 3;
+               }
+               r = read(0, &c, 1);
+               if (r == 1)
+                       return c;
+               if (fail++ > 100)
+                       break;
+       }
+       return EOF;
+}
+#endif
+
+static int ask_yn(const char * string, int def)
+{
+       int             c;
+       const char      *defstr;
+       static const char short_yes[] = "yY";
+       static const char short_no[] = "nN";
+
+#ifdef HAVE_TERMIOS_H
+       struct termios  termios, tmp;
+
+       tcgetattr (0, &termios);
+       tmp = termios;
+       tmp.c_lflag &= ~(ICANON | ECHO);
+       tmp.c_cc[VMIN] = 1;
+       tmp.c_cc[VTIME] = 0;
+       tcsetattr (0, TCSANOW, &tmp);
+#endif
+
+       if (def == 1)
+               defstr = "<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 (0, TCSANOW, &termios);
+#endif
+                       if (e2fsck_global_ctx &&
+                           e2fsck_global_ctx->flags & E2F_FLAG_SETJMP_OK) {
+                               puts("\n");
+                               longjmp(e2fsck_global_ctx->abort_loc, 1);
+                       }
+                       puts(_("cancelled!\n"));
+                       return 0;
+               }
+               if (strchr(short_yes, (char) c)) {
+                       def = 1;
+                       break;
+               }
+               else if (strchr(short_no, (char) c)) {
+                       def = 0;
+                       break;
+               }
+               else if ((c == ' ' || c == '\n') && (def != -1))
+                       break;
+       }
+       if (def)
+               puts("yes\n");
+       else
+               puts ("no\n");
+#ifdef HAVE_TERMIOS_H
+       tcsetattr (0, TCSANOW, &termios);
+#endif
+       return def;
+}
+
+int ask (e2fsck_t ctx, const char * string, int def)
+{
+       if (ctx->options & E2F_OPT_NO) {
+               printf(_("%s? no\n\n"), string);
+               return 0;
+       }
+       if (ctx->options & E2F_OPT_YES) {
+               printf(_("%s? yes\n\n"), string);
+               return 1;
+       }
+       if (ctx->options & E2F_OPT_PREEN) {
+               printf("%s? %s\n\n", string, def ? _("yes") : _("no"));
+               return def;
+       }
+       return ask_yn(string, def);
+}
+
+void e2fsck_read_bitmaps(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       errcode_t       retval;
+
+       if (ctx->invalid_bitmaps) {
+               bb_error_msg(_("e2fsck_read_bitmaps: illegal bitmap block(s) for %s"),
+                       ctx->device_name);
+               bb_error_msg_and_die(0);
+       }
+
+       ehandler_operation(_("reading inode and block bitmaps"));
+       retval = ext2fs_read_bitmaps(fs);
+       ehandler_operation(0);
+       if (retval) {
+               bb_error_msg(_("while retrying to read bitmaps for %s"),
+                       ctx->device_name);
+               bb_error_msg_and_die(0);
+       }
+}
+
+static void e2fsck_write_bitmaps(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       errcode_t       retval;
+
+       if (ext2fs_test_bb_dirty(fs)) {
+               ehandler_operation(_("writing block bitmaps"));
+               retval = ext2fs_write_block_bitmap(fs);
+               ehandler_operation(0);
+               if (retval) {
+                       bb_error_msg(_("while retrying to write block bitmaps for %s"),
+                               ctx->device_name);
+                       bb_error_msg_and_die(0);
+               }
+       }
+
+       if (ext2fs_test_ib_dirty(fs)) {
+               ehandler_operation(_("writing inode bitmaps"));
+               retval = ext2fs_write_inode_bitmap(fs);
+               ehandler_operation(0);
+               if (retval) {
+                       bb_error_msg(_("while retrying to write inode bitmaps for %s"),
+                               ctx->device_name);
+                       bb_error_msg_and_die(0);
+               }
+       }
+}
+
+void preenhalt(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+
+       if (!(ctx->options & E2F_OPT_PREEN))
+               return;
+       fprintf(stderr, _("\n\n%s: UNEXPECTED INCONSISTENCY; "
+               "RUN fsck MANUALLY.\n\t(i.e., without -a or -p options)\n"),
+              ctx->device_name);
+       if (fs != NULL) {
+               fs->super->s_state |= EXT2_ERROR_FS;
+               ext2fs_mark_super_dirty(fs);
+               ext2fs_close(fs);
+       }
+       exit(EXIT_UNCORRECTED);
+}
+
+void e2fsck_read_inode(e2fsck_t ctx, unsigned long ino,
+                             struct ext2_inode * inode, const char *proc)
+{
+       int retval;
+
+       retval = ext2fs_read_inode(ctx->fs, ino, inode);
+       if (retval) {
+               bb_error_msg(_("while reading inode %ld in %s"), ino, proc);
+               bb_error_msg_and_die(0);
+       }
+}
+
+extern void e2fsck_write_inode_full(e2fsck_t ctx, unsigned long ino,
+                              struct ext2_inode * inode, int bufsize,
+                              const char *proc)
+{
+       int retval;
+
+       retval = ext2fs_write_inode_full(ctx->fs, ino, inode, bufsize);
+       if (retval) {
+               bb_error_msg(_("while writing inode %ld in %s"), ino, proc);
+               bb_error_msg_and_die(0);
+       }
+}
+
+extern void e2fsck_write_inode(e2fsck_t ctx, unsigned long ino,
+                              struct ext2_inode * inode, const char *proc)
+{
+       int retval;
+
+       retval = ext2fs_write_inode(ctx->fs, ino, inode);
+       if (retval) {
+               bb_error_msg(_("while writing inode %ld in %s"), ino, proc);
+               bb_error_msg_and_die(0);
+       }
+}
+
+blk_t get_backup_sb(e2fsck_t ctx, ext2_filsys fs, const char *name,
+                  io_manager manager)
+{
+       struct ext2_super_block *sb;
+       io_channel              io = NULL;
+       void                    *buf = NULL;
+       int                     blocksize;
+       blk_t                   superblock, ret_sb = 8193;
+
+       if (fs && fs->super) {
+               ret_sb = (fs->super->s_blocks_per_group +
+                         fs->super->s_first_data_block);
+               if (ctx) {
+                       ctx->superblock = ret_sb;
+                       ctx->blocksize = fs->blocksize;
+               }
+               return ret_sb;
+       }
+
+       if (ctx) {
+               if (ctx->blocksize) {
+                       ret_sb = ctx->blocksize * 8;
+                       if (ctx->blocksize == 1024)
+                               ret_sb++;
+                       ctx->superblock = ret_sb;
+                       return ret_sb;
+               }
+               ctx->superblock = ret_sb;
+               ctx->blocksize = 1024;
+       }
+
+       if (!name || !manager)
+               goto cleanup;
+
+       if (manager->open(name, 0, &io) != 0)
+               goto cleanup;
+
+       if (ext2fs_get_mem(SUPERBLOCK_SIZE, &buf))
+               goto cleanup;
+       sb = (struct ext2_super_block *) buf;
+
+       for (blocksize = EXT2_MIN_BLOCK_SIZE;
+            blocksize <= EXT2_MAX_BLOCK_SIZE; blocksize *= 2) {
+               superblock = blocksize*8;
+               if (blocksize == 1024)
+                       superblock++;
+               io_channel_set_blksize(io, blocksize);
+               if (io_channel_read_blk(io, superblock,
+                                       -SUPERBLOCK_SIZE, buf))
+                       continue;
+#if BB_BIG_ENDIAN
+               if (sb->s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC))
+                       ext2fs_swap_super(sb);
+#endif
+               if (sb->s_magic == EXT2_SUPER_MAGIC) {
+                       ret_sb = superblock;
+                       if (ctx) {
+                               ctx->superblock = superblock;
+                               ctx->blocksize = blocksize;
+                       }
+                       break;
+               }
+       }
+
+cleanup:
+       if (io)
+               io_channel_close(io);
+       ext2fs_free_mem(&buf);
+       return ret_sb;
+}
+
+
+/*
+ * This function runs through the e2fsck passes and calls them all,
+ * returning restart, abort, or cancel as necessary...
+ */
+typedef void (*pass_t)(e2fsck_t ctx);
+
+static const pass_t e2fsck_passes[] = {
+       e2fsck_pass1, e2fsck_pass2, e2fsck_pass3, e2fsck_pass4,
+       e2fsck_pass5, 0 };
+
+#define E2F_FLAG_RUN_RETURN     (E2F_FLAG_SIGNAL_MASK|E2F_FLAG_RESTART)
+
+static int e2fsck_run(e2fsck_t ctx)
+{
+       int     i;
+       pass_t  e2fsck_pass;
+
+       if (setjmp(ctx->abort_loc)) {
+               ctx->flags &= ~E2F_FLAG_SETJMP_OK;
+               return (ctx->flags & E2F_FLAG_RUN_RETURN);
+       }
+       ctx->flags |= E2F_FLAG_SETJMP_OK;
+
+       for (i=0; (e2fsck_pass = e2fsck_passes[i]); i++) {
+               if (ctx->flags & E2F_FLAG_RUN_RETURN)
+                       break;
+               e2fsck_pass(ctx);
+               if (ctx->progress)
+                       (void) (ctx->progress)(ctx, 0, 0, 0);
+       }
+       ctx->flags &= ~E2F_FLAG_SETJMP_OK;
+
+       if (ctx->flags & E2F_FLAG_RUN_RETURN)
+               return (ctx->flags & E2F_FLAG_RUN_RETURN);
+       return 0;
+}
+
+
+/*
+ * unix.c - The unix-specific code for e2fsck
+ */
+
+
+/* Command line options */
+static int swapfs;
+#ifdef ENABLE_SWAPFS
+static int normalize_swapfs;
+#endif
+static int cflag;               /* check disk */
+static int show_version_only;
+static int verbose;
+
+#define P_E2(singular, plural, n)       n, ((n) == 1 ? singular : plural)
+
+static void show_stats(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       int inodes, inodes_used, blocks, blocks_used;
+       int dir_links;
+       int num_files, num_links;
+       int frag_percent;
+
+       dir_links = 2 * ctx->fs_directory_count - 1;
+       num_files = ctx->fs_total_count - dir_links;
+       num_links = ctx->fs_links_count - dir_links;
+       inodes = fs->super->s_inodes_count;
+       inodes_used = (fs->super->s_inodes_count -
+                      fs->super->s_free_inodes_count);
+       blocks = fs->super->s_blocks_count;
+       blocks_used = (fs->super->s_blocks_count -
+                      fs->super->s_free_blocks_count);
+
+       frag_percent = (10000 * ctx->fs_fragmented) / inodes_used;
+       frag_percent = (frag_percent + 5) / 10;
+
+       if (!verbose) {
+               printf("%s: %d/%d files (%0d.%d%% non-contiguous), %d/%d blocks\n",
+                      ctx->device_name, inodes_used, inodes,
+                      frag_percent / 10, frag_percent % 10,
+                      blocks_used, blocks);
+               return;
+       }
+       printf("\n%8d inode%s used (%d%%)\n", P_E2("", "s", inodes_used),
+               100 * inodes_used / inodes);
+       printf("%8d non-contiguous inode%s (%0d.%d%%)\n",
+               P_E2("", "s", ctx->fs_fragmented),
+               frag_percent / 10, frag_percent % 10);
+       printf(_("         # of inodes with ind/dind/tind blocks: %d/%d/%d\n"),
+               ctx->fs_ind_count, ctx->fs_dind_count, ctx->fs_tind_count);
+       printf("%8d block%s used (%d%%)\n", P_E2("", "s", blocks_used),
+               (int) ((long long) 100 * blocks_used / blocks));
+       printf("%8d large file%s\n", P_E2("", "s", ctx->large_files));
+       printf("\n%8d regular file%s\n", P_E2("", "s", ctx->fs_regular_count));
+       printf("%8d director%s\n", P_E2("y", "ies", ctx->fs_directory_count));
+       printf("%8d character device file%s\n", P_E2("", "s", ctx->fs_chardev_count));
+       printf("%8d block device file%s\n", P_E2("", "s", ctx->fs_blockdev_count));
+       printf("%8d fifo%s\n", P_E2("", "s", ctx->fs_fifo_count));
+       printf("%8d link%s\n", P_E2("", "s", ctx->fs_links_count - dir_links));
+       printf("%8d symbolic link%s", P_E2("", "s", ctx->fs_symlinks_count));
+       printf(" (%d fast symbolic link%s)\n", P_E2("", "s", ctx->fs_fast_symlinks_count));
+       printf("%8d socket%s--------\n\n", P_E2("", "s", ctx->fs_sockets_count));
+       printf("%8d file%s\n", P_E2("", "s", ctx->fs_total_count - dir_links));
+}
+
+static void check_mount(e2fsck_t ctx)
+{
+       errcode_t       retval;
+       int             cont;
+
+       retval = ext2fs_check_if_mounted(ctx->filesystem_name,
+                                        &ctx->mount_flags);
+       if (retval) {
+               bb_error_msg(_("while determining whether %s is mounted."),
+                       ctx->filesystem_name);
+               return;
+       }
+
+       /*
+        * If the filesystem isn't mounted, or it's the root filesystem
+        * and it's mounted read-only, then everything's fine.
+        */
+       if ((!(ctx->mount_flags & EXT2_MF_MOUNTED)) ||
+           ((ctx->mount_flags & EXT2_MF_ISROOT) &&
+            (ctx->mount_flags & EXT2_MF_READONLY)))
+               return;
+
+       if (ctx->options & E2F_OPT_READONLY) {
+               printf(_("Warning!  %s is mounted.\n"), ctx->filesystem_name);
+               return;
+       }
+
+       printf(_("%s is mounted.  "), ctx->filesystem_name);
+       if (!ctx->interactive)
+               bb_error_msg_and_die(_("Cannot continue, aborting."));
+       printf(_("\n\n\007\007\007\007WARNING!!!  "
+              "Running e2fsck on a mounted filesystem may cause\n"
+              "SEVERE filesystem damage.\007\007\007\n\n"));
+       cont = ask_yn(_("Do you really want to continue"), -1);
+       if (!cont) {
+               printf(_("check aborted.\n"));
+               exit(0);
+       }
+}
+
+static int is_on_batt(void)
+{
+       FILE    *f;
+       DIR     *d;
+       char    tmp[80], tmp2[80], fname[80];
+       unsigned int    acflag;
+       struct dirent*  de;
+
+       f = fopen("/proc/apm", "r");
+       if (f) {
+               if (fscanf(f, "%s %s %s %x", tmp, tmp, tmp, &acflag) != 4)
+                       acflag = 1;
+               fclose(f);
+               return (acflag != 1);
+       }
+       d = opendir("/proc/acpi/ac_adapter");
+       if (d) {
+               while ((de=readdir(d)) != NULL) {
+                       if (!strncmp(".", de->d_name, 1))
+                               continue;
+                       snprintf(fname, 80, "/proc/acpi/ac_adapter/%s/state",
+                                de->d_name);
+                       f = fopen(fname, "r");
+                       if (!f)
+                               continue;
+                       if (fscanf(f, "%s %s", tmp2, tmp) != 2)
+                               tmp[0] = 0;
+                       fclose(f);
+                       if (strncmp(tmp, "off-line", 8) == 0) {
+                               closedir(d);
+                               return 1;
+                       }
+               }
+               closedir(d);
+       }
+       return 0;
+}
+
+/*
+ * This routine checks to see if a filesystem can be skipped; if so,
+ * it will exit with EXIT_OK.  Under some conditions it will print a
+ * message explaining why a check is being forced.
+ */
+static void check_if_skip(e2fsck_t ctx)
+{
+       ext2_filsys fs = ctx->fs;
+       const char *reason = NULL;
+       unsigned int reason_arg = 0;
+       long next_check;
+       int batt = is_on_batt();
+       time_t now = time(0);
+
+       if ((ctx->options & E2F_OPT_FORCE) || cflag || swapfs)
+               return;
+
+       if ((fs->super->s_state & EXT2_ERROR_FS) ||
+           !ext2fs_test_valid(fs))
+               reason = _(" contains a file system with errors");
+       else if ((fs->super->s_state & EXT2_VALID_FS) == 0)
+               reason = _(" was not cleanly unmounted");
+       else if ((fs->super->s_max_mnt_count > 0) &&
+                (fs->super->s_mnt_count >=
+                 (unsigned) fs->super->s_max_mnt_count)) {
+               reason = _(" has been mounted %u times without being checked");
+               reason_arg = fs->super->s_mnt_count;
+               if (batt && (fs->super->s_mnt_count <
+                            (unsigned) fs->super->s_max_mnt_count*2))
+                       reason = 0;
+       } else if (fs->super->s_checkinterval &&
+                  ((now - fs->super->s_lastcheck) >=
+                   fs->super->s_checkinterval)) {
+               reason = _(" has gone %u days without being checked");
+               reason_arg = (now - fs->super->s_lastcheck)/(3600*24);
+               if (batt && ((now - fs->super->s_lastcheck) <
+                            fs->super->s_checkinterval*2))
+                       reason = 0;
+       }
+       if (reason) {
+               fputs(ctx->device_name, stdout);
+               printf(reason, reason_arg);
+               fputs(_(", check forced.\n"), stdout);
+               return;
+       }
+       printf(_("%s: clean, %d/%d files, %d/%d blocks"), ctx->device_name,
+              fs->super->s_inodes_count - fs->super->s_free_inodes_count,
+              fs->super->s_inodes_count,
+              fs->super->s_blocks_count - fs->super->s_free_blocks_count,
+              fs->super->s_blocks_count);
+       next_check = 100000;
+       if (fs->super->s_max_mnt_count > 0) {
+               next_check = fs->super->s_max_mnt_count - fs->super->s_mnt_count;
+               if (next_check <= 0)
+                       next_check = 1;
+       }
+       if (fs->super->s_checkinterval &&
+           ((now - fs->super->s_lastcheck) >= fs->super->s_checkinterval))
+               next_check = 1;
+       if (next_check <= 5) {
+               if (next_check == 1)
+                       fputs(_(" (check after next mount)"), stdout);
+               else
+                       printf(_(" (check in %ld mounts)"), next_check);
+       }
+       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);
+               write(ctx->progress_fd, buf, strlen(buf));
+       } else {
+               percent = calc_percent(&e2fsck_tbl, pass, cur, max);
+               e2fsck_simple_progress(ctx, ctx->device_name,
+                                      percent, 0);
+       }
+       return 0;
+}
+
+static void reserve_stdio_fds(void)
+{
+       int     fd;
+
+       while (1) {
+               fd = open(bb_dev_null, O_RDWR);
+               if (fd > 2)
+                       break;
+               if (fd < 0) {
+                       fprintf(stderr, _("ERROR: Cannot open "
+                               "/dev/null (%s)\n"),
+                               strerror(errno));
+                       break;
+               }
+       }
+       close(fd);
+}
+
+static void signal_progress_on(int sig FSCK_ATTR((unused)))
+{
+       e2fsck_t ctx = e2fsck_global_ctx;
+
+       if (!ctx)
+               return;
+
+       ctx->progress = e2fsck_update_progress;
+       ctx->progress_fd = 0;
+}
+
+static void signal_progress_off(int sig FSCK_ATTR((unused)))
+{
+       e2fsck_t ctx = e2fsck_global_ctx;
+
+       if (!ctx)
+               return;
+
+       e2fsck_clear_progbar(ctx);
+       ctx->progress = 0;
+}
+
+static void signal_cancel(int sig FSCK_ATTR((unused)))
+{
+       e2fsck_t ctx = e2fsck_global_ctx;
+
+       if (!ctx)
+               exit(FSCK_CANCELED);
+
+       ctx->flags |= E2F_FLAG_CANCEL;
+}
+
+static void parse_extended_opts(e2fsck_t ctx, const char *opts)
+{
+       char    *buf, *token, *next, *p, *arg;
+       int     ea_ver;
+       int     extended_usage = 0;
+
+       buf = string_copy(opts, 0);
+       for (token = buf; token && *token; token = next) {
+               p = strchr(token, ',');
+               next = 0;
+               if (p) {
+                       *p = 0;
+                       next = p+1;
+               }
+               arg = strchr(token, '=');
+               if (arg) {
+                       *arg = 0;
+                       arg++;
+               }
+               if (strcmp(token, "ea_ver") == 0) {
+                       if (!arg) {
+                               extended_usage++;
+                               continue;
+                       }
+                       ea_ver = strtoul(arg, &p, 0);
+                       if (*p ||
+                           ((ea_ver != 1) && (ea_ver != 2))) {
+                               fprintf(stderr,
+                                       _("Invalid EA version.\n"));
+                               extended_usage++;
+                               continue;
+                       }
+                       ctx->ext_attr_ver = ea_ver;
+               } else {
+                       fprintf(stderr, _("Unknown extended option: %s\n"),
+                               token);
+                       extended_usage++;
+               }
+       }
+       if (extended_usage) {
+               bb_error_msg_and_die(
+                       "Extended options are separated by commas, "
+                       "and may take an argument which\n"
+                       "is set off by an equals ('=') sign.  "
+                       "Valid extended options are:\n"
+                       "\tea_ver=<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..cae28f1
--- /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_flags() */
+
+#define PFOPT_LONG  1 /* Must be 1 for compatibility with `int long_format'. */
+
+/*int fgetversion (const char * name, unsigned long * version);*/
+/*int fsetversion (const char * name, unsigned long version);*/
+int fgetsetversion(const char * name, unsigned long * get_version, unsigned long set_version);
+#define fgetversion(name, version) fgetsetversion(name, version, 0)
+#define fsetversion(name, version) fgetsetversion(name, NULL, version)
+
+/*int fgetflags (const char * name, unsigned long * flags);*/
+/*int fsetflags (const char * name, unsigned long flags);*/
+int fgetsetflags(const char * name, unsigned long * get_flags, unsigned long set_flags);
+#define fgetflags(name, flags) fgetsetflags(name, flags, 0)
+#define fsetflags(name, flags) fgetsetflags(name, NULL, flags)
+
+int getflags (int fd, unsigned long * flags);
+int getversion (int fd, unsigned long * version);
+int iterate_on_dir (const char * dir_name,
+                   int (*func) (const char *, struct dirent *, void *),
+                   void * private);
+/*void list_super(struct ext2_super_block * s);*/
+void list_super2(struct ext2_super_block * s, FILE *f);
+#define list_super(s) list_super2(s, stdout)
+void print_fs_errors (FILE * f, unsigned short errors);
+void print_flags (FILE * f, unsigned long flags, unsigned options);
+void print_fs_state (FILE * f, unsigned short state);
+int setflags (int fd, unsigned long flags);
+int setversion (int fd, unsigned long version);
+
+const char *e2p_feature2string(int compat, unsigned int mask);
+int e2p_string2feature(char *string, int *compat, unsigned int *mask);
+int e2p_edit_feature(const char *str, __u32 *compat_array, __u32 *ok_array);
+
+int e2p_is_null_uuid(void *uu);
+void e2p_uuid_to_str(void *uu, char *out);
+const char *e2p_uuid2str(void *uu);
+
+const char *e2p_hash2string(int num);
+int e2p_string2hash(char *string);
+
+const char *e2p_mntopt2string(unsigned int mask);
+int e2p_string2mntopt(char *string, unsigned int *mask);
+int e2p_edit_mntopts(const char *str, __u32 *mntopts, __u32 ok);
+
+unsigned long parse_num_blocks(const char *arg, int log_block_size);
+
+char *e2p_os2string(int os_type);
+int e2p_string2os(char *str);
diff --git a/e2fsprogs/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..55d4bc4
--- /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..855f86e
--- /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(0);
+       if (!inode.i_ctime)
+               inode.i_ctime = time(0);
+       inode.i_blocks = rec.bad_block_count * (fs->blocksize / 512);
+       inode.i_size = rec.bad_block_count * fs->blocksize;
+
+       retval = ext2fs_write_inode(fs, EXT2_BAD_INO, &inode);
+       if (retval)
+               goto cleanup;
+
+cleanup:
+       ext2fs_free_mem(&rec.ind_blocks);
+       ext2fs_free_mem(&rec.block_buf);
+       return retval;
+}
+
+/*
+ * Helper function for update_bb_inode()
+ *
+ * Clear the bad blocks in the bad block inode, while saving the
+ * indirect blocks.
+ */
+#ifdef __TURBOC__
+# pragma argsused
+#endif
+static int clear_bad_block_proc(ext2_filsys fs, blk_t *block_nr,
+                               e2_blkcnt_t blockcnt,
+                               blk_t ref_block EXT2FS_ATTR((unused)),
+                               int ref_offset EXT2FS_ATTR((unused)),
+                               void *priv_data)
+{
+       struct set_badblock_record *rec = (struct set_badblock_record *)
+               priv_data;
+       errcode_t       retval;
+       unsigned long   old_size;
+
+       if (!*block_nr)
+               return 0;
+
+       /*
+        * If the block number is outrageous, clear it and ignore it.
+        */
+       if (*block_nr >= fs->super->s_blocks_count ||
+           *block_nr < fs->super->s_first_data_block) {
+               *block_nr = 0;
+               return BLOCK_CHANGED;
+       }
+
+       if (blockcnt < 0) {
+               if (rec->ind_blocks_size >= rec->max_ind_blocks) {
+                       old_size = rec->max_ind_blocks * sizeof(blk_t);
+                       rec->max_ind_blocks += 10;
+                       retval = ext2fs_resize_mem(old_size,
+                                  rec->max_ind_blocks * sizeof(blk_t),
+                                  &rec->ind_blocks);
+                       if (retval) {
+                               rec->max_ind_blocks -= 10;
+                               rec->err = retval;
+                               return BLOCK_ABORT;
+                       }
+               }
+               rec->ind_blocks[rec->ind_blocks_size++] = *block_nr;
+       }
+
+       /*
+        * Mark the block as unused, and update accounting information
+        */
+       ext2fs_block_alloc_stats(fs, *block_nr, -1);
+
+       *block_nr = 0;
+       return BLOCK_CHANGED;
+}
+
+
+/*
+ * Helper function for update_bb_inode()
+ *
+ * Set the block list in the bad block inode, using the supplied bitmap.
+ */
+#ifdef __TURBOC__
+ #pragma argsused
+#endif
+static int set_bad_block_proc(ext2_filsys fs, blk_t *block_nr,
+                             e2_blkcnt_t blockcnt,
+                             blk_t ref_block EXT2FS_ATTR((unused)),
+                             int ref_offset EXT2FS_ATTR((unused)),
+                             void *priv_data)
+{
+       struct set_badblock_record *rec = (struct set_badblock_record *)
+               priv_data;
+       errcode_t       retval;
+       blk_t           blk;
+
+       if (blockcnt >= 0) {
+               /*
+                * Get the next bad block.
+                */
+               if (!ext2fs_badblocks_list_iterate(rec->bb_iter, &blk))
+                       return BLOCK_ABORT;
+               rec->bad_block_count++;
+       } else {
+               /*
+                * An indirect block; fetch a block from the
+                * previously used indirect block list.  The block
+                * most be not marked as used; if so, get another one.
+                * If we run out of reserved indirect blocks, allocate
+                * a new one.
+                */
+       retry:
+               if (rec->ind_blocks_ptr < rec->ind_blocks_size) {
+                       blk = rec->ind_blocks[rec->ind_blocks_ptr++];
+                       if (ext2fs_test_block_bitmap(fs->block_map, blk))
+                               goto retry;
+               } else {
+                       retval = ext2fs_new_block(fs, 0, 0, &blk);
+                       if (retval) {
+                               rec->err = retval;
+                               return BLOCK_ABORT;
+                       }
+               }
+               retval = io_channel_write_blk(fs->io, blk, 1, rec->block_buf);
+               if (retval) {
+                       rec->err = retval;
+                       return BLOCK_ABORT;
+               }
+       }
+
+       /*
+        * Update block counts
+        */
+       ext2fs_block_alloc_stats(fs, blk, +1);
+
+       *block_nr = blk;
+       return BLOCK_CHANGED;
+}
+
+
+
+
+
+
diff --git a/e2fsprogs/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..ab3243f
--- /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..cb49d7a
--- /dev/null
@@ -0,0 +1,570 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  linux/include/linux/ext2_fs.h
+ *
+ * Copyright (C) 1992, 1993, 1994, 1995
+ * Remy Card (card@masi.ibp.fr)
+ * Laboratoire MASI - Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ *  from
+ *
+ *  linux/include/linux/minix_fs.h
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ */
+
+#ifndef _LINUX_EXT2_FS_H
+#define _LINUX_EXT2_FS_H
+
+#include "ext2_types.h"                /* Changed from linux/types.h */
+
+/*
+ * Special inode numbers
+ */
+#define EXT2_BAD_INO            1      /* Bad blocks inode */
+#define EXT2_ROOT_INO           2      /* Root inode */
+#define EXT2_ACL_IDX_INO        3      /* ACL inode */
+#define EXT2_ACL_DATA_INO       4      /* ACL inode */
+#define EXT2_BOOT_LOADER_INO    5      /* Boot loader inode */
+#define EXT2_UNDEL_DIR_INO      6      /* Undelete directory inode */
+#define EXT2_RESIZE_INO                 7      /* Reserved group descriptors inode */
+#define EXT2_JOURNAL_INO        8      /* Journal inode */
+
+/* First non-reserved inode for old ext2 filesystems */
+#define EXT2_GOOD_OLD_FIRST_INO        11
+
+/*
+ * The second extended file system magic number
+ */
+#define EXT2_SUPER_MAGIC       0xEF53
+
+/* Assume that user mode programs are passing in an ext2fs superblock, not
+ * a kernel struct super_block.  This will allow us to call the feature-test
+ * macros from user land. */
+#define EXT2_SB(sb)    (sb)
+
+/*
+ * Maximal count of links to a file
+ */
+#define EXT2_LINK_MAX          32000
+
+/*
+ * Macro-instructions used to manage several block sizes
+ */
+#define EXT2_MIN_BLOCK_LOG_SIZE                10      /* 1024 */
+#define EXT2_MAX_BLOCK_LOG_SIZE                16      /* 65536 */
+#define EXT2_MIN_BLOCK_SIZE    (1 << EXT2_MIN_BLOCK_LOG_SIZE)
+#define EXT2_MAX_BLOCK_SIZE    (1 << EXT2_MAX_BLOCK_LOG_SIZE)
+#define EXT2_BLOCK_SIZE(s)     (EXT2_MIN_BLOCK_SIZE << (s)->s_log_block_size)
+#define EXT2_BLOCK_SIZE_BITS(s)        ((s)->s_log_block_size + 10)
+#define EXT2_INODE_SIZE(s)     (((s)->s_rev_level == EXT2_GOOD_OLD_REV) ? \
+                                EXT2_GOOD_OLD_INODE_SIZE : (s)->s_inode_size)
+#define EXT2_FIRST_INO(s)      (((s)->s_rev_level == EXT2_GOOD_OLD_REV) ? \
+                                EXT2_GOOD_OLD_FIRST_INO : (s)->s_first_ino)
+#define EXT2_ADDR_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / sizeof(__u32))
+
+/*
+ * Macro-instructions used to manage fragments
+ */
+#define EXT2_MIN_FRAG_SIZE             EXT2_MIN_BLOCK_SIZE
+#define EXT2_MAX_FRAG_SIZE             EXT2_MAX_BLOCK_SIZE
+#define EXT2_MIN_FRAG_LOG_SIZE         EXT2_MIN_BLOCK_LOG_SIZE
+# define EXT2_FRAG_SIZE(s)             (EXT2_MIN_FRAG_SIZE << (s)->s_log_frag_size)
+# define EXT2_FRAGS_PER_BLOCK(s)       (EXT2_BLOCK_SIZE(s) / EXT2_FRAG_SIZE(s))
+
+/*
+ * ACL structures
+ */
+struct ext2_acl_header /* Header of Access Control Lists */
+{
+       __u32   aclh_size;
+       __u32   aclh_file_count;
+       __u32   aclh_acle_count;
+       __u32   aclh_first_acle;
+};
+
+struct ext2_acl_entry  /* Access Control List Entry */
+{
+       __u32   acle_size;
+       __u16   acle_perms;     /* Access permissions */
+       __u16   acle_type;      /* Type of entry */
+       __u16   acle_tag;       /* User or group identity */
+       __u16   acle_pad1;
+       __u32   acle_next;      /* Pointer on next entry for the */
+                                       /* same inode or on next free entry */
+};
+
+/*
+ * Structure of a blocks group descriptor
+ */
+struct ext2_group_desc
+{
+       __u32   bg_block_bitmap;                /* Blocks bitmap block */
+       __u32   bg_inode_bitmap;                /* Inodes bitmap block */
+       __u32   bg_inode_table;         /* Inodes table block */
+       __u16   bg_free_blocks_count;   /* Free blocks count */
+       __u16   bg_free_inodes_count;   /* Free inodes count */
+       __u16   bg_used_dirs_count;     /* Directories count */
+       __u16   bg_pad;
+       __u32   bg_reserved[3];
+};
+
+/*
+ * Data structures used by the directory indexing feature
+ *
+ * Note: all of the multibyte integer fields are little endian.
+ */
+
+/*
+ * Note: dx_root_info is laid out so that if it should somehow get
+ * overlaid by a dirent the two low bits of the hash version will be
+ * zero.  Therefore, the hash version mod 4 should never be 0.
+ * Sincerely, the paranoia department.
+ */
+struct ext2_dx_root_info {
+       __u32 reserved_zero;
+       __u8 hash_version; /* 0 now, 1 at release */
+       __u8 info_length; /* 8 */
+       __u8 indirect_levels;
+       __u8 unused_flags;
+};
+
+#define EXT2_HASH_LEGACY       0
+#define EXT2_HASH_HALF_MD4     1
+#define EXT2_HASH_TEA          2
+
+#define EXT2_HASH_FLAG_INCOMPAT        0x1
+
+struct ext2_dx_entry {
+       __u32 hash;
+       __u32 block;
+};
+
+struct ext2_dx_countlimit {
+       __u16 limit;
+       __u16 count;
+};
+
+
+/*
+ * Macro-instructions used to manage group descriptors
+ */
+#define EXT2_BLOCKS_PER_GROUP(s)       (EXT2_SB(s)->s_blocks_per_group)
+#define EXT2_INODES_PER_GROUP(s)       (EXT2_SB(s)->s_inodes_per_group)
+#define EXT2_INODES_PER_BLOCK(s)       (EXT2_BLOCK_SIZE(s)/EXT2_INODE_SIZE(s))
+/* limits imposed by 16-bit value gd_free_{blocks,inode}_count */
+#define EXT2_MAX_BLOCKS_PER_GROUP(s)   ((1 << 16) - 8)
+#define EXT2_MAX_INODES_PER_GROUP(s)   ((1 << 16) - EXT2_INODES_PER_BLOCK(s))
+#define EXT2_DESC_PER_BLOCK(s)         (EXT2_BLOCK_SIZE(s) / sizeof (struct ext2_group_desc))
+
+/*
+ * Constants relative to the data blocks
+ */
+#define EXT2_NDIR_BLOCKS               12
+#define EXT2_IND_BLOCK                 EXT2_NDIR_BLOCKS
+#define EXT2_DIND_BLOCK                        (EXT2_IND_BLOCK + 1)
+#define EXT2_TIND_BLOCK                        (EXT2_DIND_BLOCK + 1)
+#define EXT2_N_BLOCKS                  (EXT2_TIND_BLOCK + 1)
+
+/*
+ * Inode flags
+ */
+#define EXT2_SECRM_FL                  0x00000001 /* Secure deletion */
+#define EXT2_UNRM_FL                   0x00000002 /* Undelete */
+#define EXT2_COMPR_FL                  0x00000004 /* Compress file */
+#define EXT2_SYNC_FL                   0x00000008 /* Synchronous updates */
+#define EXT2_IMMUTABLE_FL              0x00000010 /* Immutable file */
+#define EXT2_APPEND_FL                 0x00000020 /* writes to file may only append */
+#define EXT2_NODUMP_FL                 0x00000040 /* do not dump file */
+#define EXT2_NOATIME_FL                        0x00000080 /* do not update atime */
+/* Reserved for compression usage... */
+#define EXT2_DIRTY_FL                  0x00000100
+#define EXT2_COMPRBLK_FL               0x00000200 /* One or more compressed clusters */
+#define EXT2_NOCOMPR_FL                        0x00000400 /* Access raw compressed data */
+#define EXT2_ECOMPR_FL                 0x00000800 /* Compression error */
+/* End compression flags --- maybe not all used */
+#define EXT2_BTREE_FL                  0x00001000 /* btree format dir */
+#define EXT2_INDEX_FL                  0x00001000 /* hash-indexed directory */
+#define EXT2_IMAGIC_FL                 0x00002000
+#define EXT3_JOURNAL_DATA_FL           0x00004000 /* file data should be journaled */
+#define EXT2_NOTAIL_FL                 0x00008000 /* file tail should not be merged */
+#define EXT2_DIRSYNC_FL                        0x00010000 /* Synchronous directory modifications */
+#define EXT2_TOPDIR_FL                 0x00020000 /* Top of directory hierarchies*/
+#define EXT3_EXTENTS_FL                        0x00080000 /* Inode uses extents */
+#define EXT2_RESERVED_FL               0x80000000 /* reserved for ext2 lib */
+
+#define EXT2_FL_USER_VISIBLE           0x0003DFFF /* User visible flags */
+#define EXT2_FL_USER_MODIFIABLE                0x000080FF /* User modifiable flags */
+
+/*
+ * ioctl commands
+ */
+#define EXT2_IOC_GETFLAGS              _IOR('f', 1, long)
+#define EXT2_IOC_SETFLAGS              _IOW('f', 2, long)
+#define EXT2_IOC_GETVERSION            _IOR('v', 1, long)
+#define EXT2_IOC_SETVERSION            _IOW('v', 2, long)
+
+/*
+ * Structure of an inode on the disk
+ */
+struct ext2_inode {
+       __u16   i_mode;         /* File mode */
+       __u16   i_uid;          /* Low 16 bits of Owner Uid */
+       __u32   i_size;         /* Size in bytes */
+       __u32   i_atime;        /* Access time */
+       __u32   i_ctime;        /* Creation time */
+       __u32   i_mtime;        /* Modification time */
+       __u32   i_dtime;        /* Deletion Time */
+       __u16   i_gid;          /* Low 16 bits of Group Id */
+       __u16   i_links_count;  /* Links count */
+       __u32   i_blocks;       /* Blocks count */
+       __u32   i_flags;        /* File flags */
+       union {
+               struct {
+                       __u32  l_i_reserved1;
+               } linux1;
+               struct {
+                       __u32  h_i_translator;
+               } hurd1;
+               struct {
+                       __u32  m_i_reserved1;
+               } masix1;
+       } osd1;                         /* OS dependent 1 */
+       __u32   i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
+       __u32   i_generation;   /* File version (for NFS) */
+       __u32   i_file_acl;     /* File ACL */
+       __u32   i_dir_acl;      /* Directory ACL */
+       __u32   i_faddr;        /* Fragment address */
+       union {
+               struct {
+                       __u8    l_i_frag;       /* Fragment number */
+                       __u8    l_i_fsize;      /* Fragment size */
+                       __u16   i_pad1;
+                       __u16   l_i_uid_high;   /* these 2 fields    */
+                       __u16   l_i_gid_high;   /* were reserved2[0] */
+                       __u32   l_i_reserved2;
+               } linux2;
+               struct {
+                       __u8    h_i_frag;       /* Fragment number */
+                       __u8    h_i_fsize;      /* Fragment size */
+                       __u16   h_i_mode_high;
+                       __u16   h_i_uid_high;
+                       __u16   h_i_gid_high;
+                       __u32   h_i_author;
+               } hurd2;
+               struct {
+                       __u8    m_i_frag;       /* Fragment number */
+                       __u8    m_i_fsize;      /* Fragment size */
+                       __u16   m_pad1;
+                       __u32   m_i_reserved2[2];
+               } masix2;
+       } osd2;                         /* OS dependent 2 */
+};
+
+/*
+ * Permanent part of an large inode on the disk
+ */
+struct ext2_inode_large {
+       __u16   i_mode;         /* File mode */
+       __u16   i_uid;          /* Low 16 bits of Owner Uid */
+       __u32   i_size;         /* Size in bytes */
+       __u32   i_atime;        /* Access time */
+       __u32   i_ctime;        /* Creation time */
+       __u32   i_mtime;        /* Modification time */
+       __u32   i_dtime;        /* Deletion Time */
+       __u16   i_gid;          /* Low 16 bits of Group Id */
+       __u16   i_links_count;  /* Links count */
+       __u32   i_blocks;       /* Blocks count */
+       __u32   i_flags;        /* File flags */
+       union {
+               struct {
+                       __u32  l_i_reserved1;
+               } linux1;
+               struct {
+                       __u32  h_i_translator;
+               } hurd1;
+               struct {
+                       __u32  m_i_reserved1;
+               } masix1;
+       } osd1;                         /* OS dependent 1 */
+       __u32   i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
+       __u32   i_generation;   /* File version (for NFS) */
+       __u32   i_file_acl;     /* File ACL */
+       __u32   i_dir_acl;      /* Directory ACL */
+       __u32   i_faddr;        /* Fragment address */
+       union {
+               struct {
+                       __u8    l_i_frag;       /* Fragment number */
+                       __u8    l_i_fsize;      /* Fragment size */
+                       __u16   i_pad1;
+                       __u16   l_i_uid_high;   /* these 2 fields    */
+                       __u16   l_i_gid_high;   /* were reserved2[0] */
+                       __u32   l_i_reserved2;
+               } linux2;
+               struct {
+                       __u8    h_i_frag;       /* Fragment number */
+                       __u8    h_i_fsize;      /* Fragment size */
+                       __u16   h_i_mode_high;
+                       __u16   h_i_uid_high;
+                       __u16   h_i_gid_high;
+                       __u32   h_i_author;
+               } hurd2;
+               struct {
+                       __u8    m_i_frag;       /* Fragment number */
+                       __u8    m_i_fsize;      /* Fragment size */
+                       __u16   m_pad1;
+                       __u32   m_i_reserved2[2];
+               } masix2;
+       } osd2;                         /* OS dependent 2 */
+       __u16   i_extra_isize;
+       __u16   i_pad1;
+};
+
+#define i_size_high    i_dir_acl
+
+/*
+ * File system states
+ */
+#define EXT2_VALID_FS                  0x0001  /* Unmounted cleanly */
+#define EXT2_ERROR_FS                  0x0002  /* Errors detected */
+
+/*
+ * Mount flags
+ */
+#define EXT2_MOUNT_CHECK               0x0001  /* Do mount-time checks */
+#define EXT2_MOUNT_GRPID               0x0004  /* Create files with directory's group */
+#define EXT2_MOUNT_DEBUG               0x0008  /* Some debugging messages */
+#define EXT2_MOUNT_ERRORS_CONT         0x0010  /* Continue on errors */
+#define EXT2_MOUNT_ERRORS_RO           0x0020  /* Remount fs ro on errors */
+#define EXT2_MOUNT_ERRORS_PANIC                0x0040  /* Panic on errors */
+#define EXT2_MOUNT_MINIX_DF            0x0080  /* Mimics the Minix statfs */
+#define EXT2_MOUNT_NO_UID32            0x0200  /* Disable 32-bit UIDs */
+
+#define clear_opt(o, opt)              o &= ~EXT2_MOUNT_##opt
+#define set_opt(o, opt)                        o |= EXT2_MOUNT_##opt
+#define test_opt(sb, opt)              (EXT2_SB(sb)->s_mount_opt & \
+                                        EXT2_MOUNT_##opt)
+/*
+ * Maximal mount counts between two filesystem checks
+ */
+#define EXT2_DFL_MAX_MNT_COUNT         20      /* Allow 20 mounts */
+#define EXT2_DFL_CHECKINTERVAL         0       /* Don't use interval check */
+
+/*
+ * Behaviour when detecting errors
+ */
+#define EXT2_ERRORS_CONTINUE           1       /* Continue execution */
+#define EXT2_ERRORS_RO                 2       /* Remount fs read-only */
+#define EXT2_ERRORS_PANIC              3       /* Panic */
+#define EXT2_ERRORS_DEFAULT            EXT2_ERRORS_CONTINUE
+
+/*
+ * Structure of the super block
+ */
+struct ext2_super_block {
+       __u32   s_inodes_count;         /* Inodes count */
+       __u32   s_blocks_count;         /* Blocks count */
+       __u32   s_r_blocks_count;       /* Reserved blocks count */
+       __u32   s_free_blocks_count;    /* Free blocks count */
+       __u32   s_free_inodes_count;    /* Free inodes count */
+       __u32   s_first_data_block;     /* First Data Block */
+       __u32   s_log_block_size;       /* Block size */
+       __s32   s_log_frag_size;        /* Fragment size */
+       __u32   s_blocks_per_group;     /* # Blocks per group */
+       __u32   s_frags_per_group;      /* # Fragments per group */
+       __u32   s_inodes_per_group;     /* # Inodes per group */
+       __u32   s_mtime;                /* Mount time */
+       __u32   s_wtime;                /* Write time */
+       __u16   s_mnt_count;            /* Mount count */
+       __s16   s_max_mnt_count;        /* Maximal mount count */
+       __u16   s_magic;                /* Magic signature */
+       __u16   s_state;                /* File system state */
+       __u16   s_errors;               /* Behaviour when detecting errors */
+       __u16   s_minor_rev_level;      /* minor revision level */
+       __u32   s_lastcheck;            /* time of last check */
+       __u32   s_checkinterval;        /* max. time between checks */
+       __u32   s_creator_os;           /* OS */
+       __u32   s_rev_level;            /* Revision level */
+       __u16   s_def_resuid;           /* Default uid for reserved blocks */
+       __u16   s_def_resgid;           /* Default gid for reserved blocks */
+       /*
+        * These fields are for EXT2_DYNAMIC_REV superblocks only.
+        *
+        * Note: the difference between the compatible feature set and
+        * the incompatible feature set is that if there is a bit set
+        * in the incompatible feature set that the kernel doesn't
+        * know about, it should refuse to mount the filesystem.
+        *
+        * e2fsck's requirements are more strict; if it doesn't know
+        * about a feature in either the compatible or incompatible
+        * feature set, it must abort and not try to meddle with
+        * things it doesn't understand...
+        */
+       __u32   s_first_ino;            /* First non-reserved inode */
+       __u16   s_inode_size;           /* size of inode structure */
+       __u16   s_block_group_nr;       /* block group # of this superblock */
+       __u32   s_feature_compat;       /* compatible feature set */
+       __u32   s_feature_incompat;     /* incompatible feature set */
+       __u32   s_feature_ro_compat;    /* readonly-compatible feature set */
+       __u8    s_uuid[16];             /* 128-bit uuid for volume */
+       char    s_volume_name[16];      /* volume name */
+       char    s_last_mounted[64];     /* directory where last mounted */
+       __u32   s_algorithm_usage_bitmap; /* For compression */
+       /*
+        * Performance hints.  Directory preallocation should only
+        * happen if the EXT2_FEATURE_COMPAT_DIR_PREALLOC flag is on.
+        */
+       __u8    s_prealloc_blocks;      /* Nr of blocks to try to preallocate*/
+       __u8    s_prealloc_dir_blocks;  /* Nr to preallocate for dirs */
+       __u16   s_reserved_gdt_blocks;  /* Per group table for online growth */
+       /*
+        * Journaling support valid if EXT2_FEATURE_COMPAT_HAS_JOURNAL set.
+        */
+       __u8    s_journal_uuid[16];     /* uuid of journal superblock */
+       __u32   s_journal_inum;         /* inode number of journal file */
+       __u32   s_journal_dev;          /* device number of journal file */
+       __u32   s_last_orphan;          /* start of list of inodes to delete */
+       __u32   s_hash_seed[4];         /* HTREE hash seed */
+       __u8    s_def_hash_version;     /* Default hash version to use */
+       __u8    s_jnl_backup_type;      /* Default type of journal backup */
+       __u16   s_reserved_word_pad;
+       __u32   s_default_mount_opts;
+       __u32   s_first_meta_bg;        /* First metablock group */
+       __u32   s_mkfs_time;            /* When the filesystem was created */
+       __u32   s_jnl_blocks[17];       /* Backup of the journal inode */
+       __u32   s_reserved[172];        /* Padding to the end of the block */
+};
+
+/*
+ * Codes for operating systems
+ */
+#define EXT2_OS_LINUX          0
+#define EXT2_OS_HURD           1
+#define EXT2_OS_MASIX          2
+#define EXT2_OS_FREEBSD                3
+#define EXT2_OS_LITES          4
+
+/*
+ * Revision levels
+ */
+#define EXT2_GOOD_OLD_REV      0       /* The good old (original) format */
+#define EXT2_DYNAMIC_REV       1       /* V2 format w/ dynamic inode sizes */
+
+#define EXT2_CURRENT_REV       EXT2_GOOD_OLD_REV
+#define EXT2_MAX_SUPP_REV      EXT2_DYNAMIC_REV
+
+#define EXT2_GOOD_OLD_INODE_SIZE 128
+
+/*
+ * Journal inode backup types
+ */
+#define EXT3_JNL_BACKUP_BLOCKS 1
+
+/*
+ * Feature set definitions
+ */
+
+#define EXT2_HAS_COMPAT_FEATURE(sb,mask)                       \
+       ( EXT2_SB(sb)->s_feature_compat & (mask) )
+#define EXT2_HAS_RO_COMPAT_FEATURE(sb,mask)                    \
+       ( EXT2_SB(sb)->s_feature_ro_compat & (mask) )
+#define EXT2_HAS_INCOMPAT_FEATURE(sb,mask)                     \
+       ( EXT2_SB(sb)->s_feature_incompat & (mask) )
+
+#define EXT2_FEATURE_COMPAT_DIR_PREALLOC       0x0001
+#define EXT2_FEATURE_COMPAT_IMAGIC_INODES      0x0002
+#define EXT3_FEATURE_COMPAT_HAS_JOURNAL                0x0004
+#define EXT2_FEATURE_COMPAT_EXT_ATTR           0x0008
+#define EXT2_FEATURE_COMPAT_RESIZE_INODE       0x0010
+#define EXT2_FEATURE_COMPAT_DIR_INDEX          0x0020
+
+#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER    0x0001
+#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE      0x0002
+/* #define EXT2_FEATURE_RO_COMPAT_BTREE_DIR    0x0004 not used */
+
+#define EXT2_FEATURE_INCOMPAT_COMPRESSION      0x0001
+#define EXT2_FEATURE_INCOMPAT_FILETYPE         0x0002
+#define EXT3_FEATURE_INCOMPAT_RECOVER          0x0004 /* Needs recovery */
+#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV      0x0008 /* Journal device */
+#define EXT2_FEATURE_INCOMPAT_META_BG          0x0010
+#define EXT3_FEATURE_INCOMPAT_EXTENTS          0x0040
+
+
+#define EXT2_FEATURE_COMPAT_SUPP       0
+#define EXT2_FEATURE_INCOMPAT_SUPP     (EXT2_FEATURE_INCOMPAT_FILETYPE)
+#define EXT2_FEATURE_RO_COMPAT_SUPP    (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
+                                        EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
+                                        EXT2_FEATURE_RO_COMPAT_BTREE_DIR)
+
+/*
+ * Default values for user and/or group using reserved blocks
+ */
+#define EXT2_DEF_RESUID                0
+#define EXT2_DEF_RESGID                0
+
+/*
+ * Default mount options
+ */
+#define EXT2_DEFM_DEBUG                0x0001
+#define EXT2_DEFM_BSDGROUPS    0x0002
+#define EXT2_DEFM_XATTR_USER   0x0004
+#define EXT2_DEFM_ACL          0x0008
+#define EXT2_DEFM_UID16                0x0010
+#define EXT3_DEFM_JMODE                0x0060
+#define EXT3_DEFM_JMODE_DATA   0x0020
+#define EXT3_DEFM_JMODE_ORDERED        0x0040
+#define EXT3_DEFM_JMODE_WBACK  0x0060
+
+/*
+ * Structure of a directory entry
+ */
+#define EXT2_NAME_LEN 255
+
+struct ext2_dir_entry {
+       __u32   inode;                  /* Inode number */
+       __u16   rec_len;                /* Directory entry length */
+       __u16   name_len;               /* Name length */
+       char    name[EXT2_NAME_LEN];    /* File name */
+};
+
+/*
+ * The new version of the directory entry.  Since EXT2 structures are
+ * stored in intel byte order, and the name_len field could never be
+ * bigger than 255 chars, it's safe to reclaim the extra byte for the
+ * file_type field.
+ */
+struct ext2_dir_entry_2 {
+       __u32   inode;                  /* Inode number */
+       __u16   rec_len;                /* Directory entry length */
+       __u8    name_len;               /* Name length */
+       __u8    file_type;
+       char    name[EXT2_NAME_LEN];    /* File name */
+};
+
+/*
+ * Ext2 directory file types.  Only the low 3 bits are used.  The
+ * other bits are reserved for now.
+ */
+#define EXT2_FT_UNKNOWN                0
+#define EXT2_FT_REG_FILE       1
+#define EXT2_FT_DIR            2
+#define EXT2_FT_CHRDEV         3
+#define EXT2_FT_BLKDEV         4
+#define EXT2_FT_FIFO           5
+#define EXT2_FT_SOCK           6
+#define EXT2_FT_SYMLINK                7
+
+#define EXT2_FT_MAX            8
+
+/*
+ * EXT2_DIR_PAD defines the directory entries boundaries
+ *
+ * NOTE: It must be a multiple of 4
+ */
+#define EXT2_DIR_PAD                   4
+#define EXT2_DIR_ROUND                 (EXT2_DIR_PAD - 1)
+#define EXT2_DIR_REC_LEN(name_len)     (((name_len) + 8 + EXT2_DIR_ROUND) & \
+                                        ~EXT2_DIR_ROUND)
+
+#endif /* _LINUX_EXT2_FS_H */
diff --git a/e2fsprogs/old_e2fsprogs/ext2fs/ext2_io.h b/e2fsprogs/old_e2fsprogs/ext2fs/ext2_io.h
new file mode 100644 (file)
index 0000000..e6c9630
--- /dev/null
@@ -0,0 +1,114 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * io.h --- the I/O manager abstraction
+ *
+ * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef _EXT2FS_EXT2_IO_H
+#define _EXT2FS_EXT2_IO_H
+
+/*
+ * ext2_loff_t is defined here since unix_io.c needs it.
+ */
+#if defined(__GNUC__) || defined(HAS_LONG_LONG)
+typedef long long      ext2_loff_t;
+#else
+typedef long           ext2_loff_t;
+#endif
+
+/* llseek.c */
+/* ext2_loff_t ext2fs_llseek (int, ext2_loff_t, int); */
+#ifdef CONFIG_LFS
+# define ext2fs_llseek lseek64
+#else
+# define ext2fs_llseek lseek
+#endif
+
+typedef struct struct_io_manager *io_manager;
+typedef struct struct_io_channel *io_channel;
+
+#define CHANNEL_FLAGS_WRITETHROUGH     0x01
+
+struct struct_io_channel {
+       errcode_t       magic;
+       io_manager      manager;
+       char            *name;
+       int             block_size;
+       errcode_t       (*read_error)(io_channel channel,
+                                     unsigned long block,
+                                     int count,
+                                     void *data,
+                                     size_t size,
+                                     int actual_bytes_read,
+                                     errcode_t error);
+       errcode_t       (*write_error)(io_channel channel,
+                                      unsigned long block,
+                                      int count,
+                                      const void *data,
+                                      size_t size,
+                                      int actual_bytes_written,
+                                      errcode_t error);
+       int             refcount;
+       int             flags;
+       int             reserved[14];
+       void            *private_data;
+       void            *app_data;
+};
+
+struct struct_io_manager {
+       errcode_t magic;
+       const char *name;
+       errcode_t (*open)(const char *name, int flags, io_channel *channel);
+       errcode_t (*close)(io_channel channel);
+       errcode_t (*set_blksize)(io_channel channel, int blksize);
+       errcode_t (*read_blk)(io_channel channel, unsigned long block,
+                             int count, void *data);
+       errcode_t (*write_blk)(io_channel channel, unsigned long block,
+                              int count, const void *data);
+       errcode_t (*flush)(io_channel channel);
+       errcode_t (*write_byte)(io_channel channel, unsigned long offset,
+                               int count, const void *data);
+       errcode_t (*set_option)(io_channel channel, const char *option,
+                               const char *arg);
+       int             reserved[14];
+};
+
+#define IO_FLAG_RW     1
+
+/*
+ * Convenience functions....
+ */
+#define io_channel_close(c)            ((c)->manager->close((c)))
+#define io_channel_set_blksize(c,s)    ((c)->manager->set_blksize((c),s))
+#define io_channel_read_blk(c,b,n,d)   ((c)->manager->read_blk((c),b,n,d))
+#define io_channel_write_blk(c,b,n,d)  ((c)->manager->write_blk((c),b,n,d))
+#define io_channel_flush(c)            ((c)->manager->flush((c)))
+#define io_channel_bumpcount(c)                ((c)->refcount++)
+
+/* io_manager.c */
+extern errcode_t io_channel_set_options(io_channel channel,
+                                       const char *options);
+extern errcode_t io_channel_write_byte(io_channel channel,
+                                      unsigned long offset,
+                                      int count, const void *data);
+
+/* unix_io.c */
+extern io_manager unix_io_manager;
+
+/* test_io.c */
+extern io_manager test_io_manager, test_io_backing_manager;
+extern void (*test_io_cb_read_blk)
+       (unsigned long block, int count, errcode_t err);
+extern void (*test_io_cb_write_blk)
+       (unsigned long block, int count, errcode_t err);
+extern void (*test_io_cb_set_blksize)
+       (int blksize, errcode_t err);
+
+#endif /* _EXT2FS_EXT2_IO_H */
+
diff --git a/e2fsprogs/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..133fb1f
--- /dev/null
@@ -0,0 +1,923 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ext2fs.h --- ext2fs
+ *
+ * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef _EXT2FS_EXT2FS_H
+#define _EXT2FS_EXT2FS_H
+
+
+#define EXT2FS_ATTR(x)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Where the master copy of the superblock is located, and how big
+ * superblocks are supposed to be.  We define SUPERBLOCK_SIZE because
+ * the size of the superblock structure is not necessarily trustworthy
+ * (some versions have the padding set up so that the superblock is
+ * 1032 bytes long).
+ */
+#define SUPERBLOCK_OFFSET      1024
+#define SUPERBLOCK_SIZE                1024
+
+/*
+ * The last ext2fs revision level that this version of the library is
+ * able to support.
+ */
+#define EXT2_LIB_CURRENT_REV   EXT2_DYNAMIC_REV
+
+#ifdef HAVE_SYS_TYPES_H
+#include <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 /* _EXT2FS_EXT2FS_H */
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..63a0dca
--- /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..d943f11
--- /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("/proc/swaps", "r")))
+               return 0;
+       /* Skip the first line */
+       fgets(buf, sizeof(buf), f);
+       while (!feof(f)) {
+               if (!fgets(buf, sizeof(buf), f))
+                       break;
+               if ((cp = strchr(buf, ' ')) != NULL)
+                       *cp = 0;
+               if ((cp = strchr(buf, '\t')) != NULL)
+                       *cp = 0;
+               if (strcmp(buf, file) == 0) {
+                       ret++;
+                       break;
+               }
+#ifndef __GNU__
+               if (file_dev && (stat(buf, &st_buf) == 0) &&
+                   S_ISBLK(st_buf.st_mode) &&
+                   file_dev == st_buf.st_rdev) {
+                       ret++;
+                       break;
+               }
+#endif /* __GNU__ */
+       }
+       fclose(f);
+       return ret;
+}
+
+
+/*
+ * ext2fs_check_mount_point() returns 1 if the device is mounted, 0
+ * otherwise.  If mtpt is non-NULL, the directory where the device is
+ * mounted is copied to where mtpt is pointing, up to mtlen
+ * characters.
+ */
+#ifdef __TURBOC__
+# pragma argsused
+#endif
+errcode_t ext2fs_check_mount_point(const char *device, int *mount_flags,
+                                 char *mtpt, int mtlen)
+{
+       if (is_swap_device(device)) {
+               *mount_flags = EXT2_MF_MOUNTED | EXT2_MF_SWAP;
+               strncpy(mtpt, "<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..4c6c7de
--- /dev/null
@@ -0,0 +1,236 @@
+/* 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
+
+#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 /* _LINUX_JBD_H */
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..3392596
--- /dev/null
@@ -0,0 +1,113 @@
+/* vi: set sw=4 ts=4: */
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+       struct list_head name = { &name, &name }
+
+#define INIT_LIST_HEAD(ptr) do { \
+       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+#if (!defined(__GNUC__) && !defined(__WATCOMC__))
+#define __inline__
+#endif
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_add(struct list_head * new,
+       struct list_head * prev,
+       struct list_head * next)
+{
+       next->prev = new;
+       new->next = next;
+       new->prev = prev;
+       prev->next = new;
+}
+
+/*
+ * Insert a new entry after the specified head..
+ */
+static __inline__ void list_add(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head, head->next);
+}
+
+/*
+ * Insert a new entry at the tail
+ */
+static __inline__ void list_add_tail(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_del(struct list_head * prev,
+                                 struct list_head * next)
+{
+       next->prev = prev;
+       prev->next = next;
+}
+
+static __inline__ void list_del(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+}
+
+static __inline__ int list_empty(struct list_head *head)
+{
+       return head->next == head;
+}
+
+/*
+ * Splice in "list" into "head"
+ */
+static __inline__ void list_splice(struct list_head *list, struct list_head *head)
+{
+       struct list_head *first = list->next;
+
+       if (first != list) {
+               struct list_head *last = list->prev;
+               struct list_head *at = head->next;
+
+               first->prev = head;
+               head->next = first;
+
+               last->next = at;
+               at->prev = last;
+       }
+}
+
+#define list_entry(ptr, type, member) \
+       ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
+
+#define list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); pos = pos->next)
+
+#endif
diff --git a/e2fsprogs/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..5bdd346
--- /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(0);
+       inode.i_links_count = 1;
+       inode.i_mode = LINUX_S_IFREG | 0600;
+
+       if ((retval = ext2fs_write_inode(fs, journal_ino, &inode)))
+               goto errout;
+       retval = 0;
+
+       memcpy(fs->super->s_jnl_blocks, inode.i_block, EXT2_N_BLOCKS*4);
+       fs->super->s_jnl_blocks[16] = inode.i_size;
+       fs->super->s_jnl_backup_type = EXT3_JNL_BACKUP_BLOCKS;
+       ext2fs_mark_super_dirty(fs);
+
+errout:
+       ext2fs_free_mem(&buf);
+       return retval;
+}
+
+/*
+ * This function adds a journal device to a filesystem
+ */
+errcode_t ext2fs_add_journal_device(ext2_filsys fs, ext2_filsys journal_dev)
+{
+       struct stat     st;
+       errcode_t       retval;
+       char            buf[1024];
+       journal_superblock_t    *jsb;
+       int             start;
+       __u32           i, nr_users;
+
+       /* Make sure the device exists and is a block device */
+       if (stat(journal_dev->device_name, &st) < 0)
+               return errno;
+
+       if (!S_ISBLK(st.st_mode))
+               return EXT2_ET_JOURNAL_NOT_BLOCK; /* Must be a block device */
+
+       /* Get the journal superblock */
+       start = 1;
+       if (journal_dev->blocksize == 1024)
+               start++;
+       if ((retval = io_channel_read_blk(journal_dev->io, start, -1024, buf)))
+               return retval;
+
+       jsb = (journal_superblock_t *) buf;
+       if ((jsb->s_header.h_magic != (unsigned) ntohl(JFS_MAGIC_NUMBER)) ||
+           (jsb->s_header.h_blocktype != (unsigned) ntohl(JFS_SUPERBLOCK_V2)))
+               return EXT2_ET_NO_JOURNAL_SB;
+
+       if (ntohl(jsb->s_blocksize) != (unsigned long) fs->blocksize)
+               return EXT2_ET_UNEXPECTED_BLOCK_SIZE;
+
+       /* Check and see if this filesystem has already been added */
+       nr_users = ntohl(jsb->s_nr_users);
+       for (i=0; i < nr_users; i++) {
+               if (memcmp(fs->super->s_uuid,
+                          &jsb->s_users[i*16], 16) == 0)
+                       break;
+       }
+       if (i >= nr_users) {
+               memcpy(&jsb->s_users[nr_users*16],
+                      fs->super->s_uuid, 16);
+               jsb->s_nr_users = htonl(nr_users+1);
+       }
+
+       /* Writeback the journal superblock */
+       if ((retval = io_channel_write_blk(journal_dev->io, start, -1024, buf)))
+               return retval;
+
+       fs->super->s_journal_inum = 0;
+       fs->super->s_journal_dev = st.st_rdev;
+       memcpy(fs->super->s_journal_uuid, jsb->s_uuid,
+              sizeof(fs->super->s_journal_uuid));
+       fs->super->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL;
+       ext2fs_mark_super_dirty(fs);
+       return 0;
+}
+
+/*
+ * This function adds a journal inode to a filesystem, using either
+ * POSIX routines if the filesystem is mounted, or using direct I/O
+ * functions if it is not.
+ */
+errcode_t ext2fs_add_journal_inode(ext2_filsys fs, blk_t size, int flags)
+{
+       errcode_t               retval;
+       ext2_ino_t              journal_ino;
+       struct stat             st;
+       char                    jfile[1024];
+       int                     fd, mount_flags, f;
+
+       retval = ext2fs_check_mount_point(fs->device_name, &mount_flags,
+                                              jfile, sizeof(jfile)-10);
+       if (retval)
+               return retval;
+
+       if (mount_flags & EXT2_MF_MOUNTED) {
+               strcat(jfile, "/.journal");
+
+               /*
+                * If .../.journal already exists, make sure any
+                * immutable or append-only flags are cleared.
+                */
+#if defined(HAVE_CHFLAGS) && defined(UF_NODUMP)
+               (void) chflags (jfile, 0);
+#else
+#if HAVE_EXT2_IOCTLS
+               fd = open(jfile, O_RDONLY);
+               if (fd >= 0) {
+                       f = 0;
+                       ioctl(fd, EXT2_IOC_SETFLAGS, &f);
+                       close(fd);
+               }
+#endif
+#endif
+
+               /* Create the journal file */
+               if ((fd = open(jfile, O_CREAT|O_WRONLY, 0600)) < 0)
+                       return errno;
+
+               if ((retval = write_journal_file(fs, jfile, size, flags)))
+                       goto errout;
+
+               /* Get inode number of the journal file */
+               if (fstat(fd, &st) < 0)
+                       goto errout;
+
+#if defined(HAVE_CHFLAGS) && defined(UF_NODUMP)
+               retval = fchflags (fd, UF_NODUMP|UF_IMMUTABLE);
+#else
+#if HAVE_EXT2_IOCTLS
+               f = EXT2_NODUMP_FL | EXT2_IMMUTABLE_FL;
+               retval = ioctl(fd, EXT2_IOC_SETFLAGS, &f);
+#endif
+#endif
+               if (retval)
+                       goto errout;
+
+               close(fd);
+               journal_ino = st.st_ino;
+       } else {
+               journal_ino = EXT2_JOURNAL_INO;
+               if ((retval = write_journal_inode(fs, journal_ino,
+                                                 size, flags)))
+                       return retval;
+       }
+
+       fs->super->s_journal_inum = journal_ino;
+       fs->super->s_journal_dev = 0;
+       memset(fs->super->s_journal_uuid, 0,
+              sizeof(fs->super->s_journal_uuid));
+       fs->super->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL;
+
+       ext2fs_mark_super_dirty(fs);
+       return 0;
+errout:
+       close(fd);
+       return retval;
+}
+
+#ifdef DEBUG
+main(int argc, char **argv)
+{
+       errcode_t       retval;
+       char            *device_name;
+       ext2_filsys     fs;
+
+       if (argc < 2) {
+               fprintf(stderr, "Usage: %s filesystem\n", argv[0]);
+               exit(1);
+       }
+       device_name = argv[1];
+
+       retval = ext2fs_open (device_name, EXT2_FLAG_RW, 0, 0,
+                             unix_io_manager, &fs);
+       if (retval) {
+               com_err(argv[0], retval, "while opening %s", device_name);
+               exit(1);
+       }
+
+       retval = ext2fs_add_journal_inode(fs, 1024);
+       if (retval) {
+               com_err(argv[0], retval, "while adding journal to %s",
+                       device_name);
+               exit(1);
+       }
+       retval = ext2fs_flush(fs);
+       if (retval) {
+               printf("Warning, had trouble writing out superblocks.\n");
+       }
+       ext2fs_close(fs);
+       exit(0);
+
+}
+#endif
diff --git a/e2fsprogs/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..f7ee8b1
--- /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(0);
+       }
+
+       for (rsv_off = 0, gdt_off = fs->desc_blocks,
+            gdt_blk = sb->s_first_data_block + 1 + fs->desc_blocks;
+            rsv_off < sb->s_reserved_gdt_blocks;
+            rsv_off++, gdt_off++, gdt_blk++) {
+               unsigned int three = 1, five = 5, seven = 7;
+               unsigned int grp, last = 0;
+               int gdt_dirty = 0;
+
+               gdt_off %= apb;
+               if (!dindir_buf[gdt_off]) {
+                       /* FIXME XXX XXX
+                       blk_t new_blk;
+
+                       retval = ext2fs_new_block(fs, gdt_blk, 0, &new_blk);
+                       if (retval)
+                               goto out_free;
+                       if (new_blk != gdt_blk) {
+                               // XXX free block
+                               retval = -1; // XXX
+                       }
+                       */
+                       gdt_dirty = dindir_dirty = inode_dirty = 1;
+                       memset(gdt_buf, 0, fs->blocksize);
+                       dindir_buf[gdt_off] = gdt_blk;
+                       inode.i_blocks += rsv_add;
+#ifdef RES_GDT_DEBUG
+                       printf("added primary GDT block %u at %u[%u]\n",
+                              gdt_blk, dindir_blk, gdt_off);
+#endif
+               } else if (dindir_buf[gdt_off] == gdt_blk) {
+#ifdef RES_GDT_DEBUG
+                       printf("reading primary GDT block %u\n", gdt_blk);
+#endif
+                       retval = ext2fs_read_ind_block(fs, gdt_blk, gdt_buf);
+                       if (retval)
+                               goto out_dindir;
+               } else {
+#ifdef RES_GDT_DEBUG
+                       printf("bad primary GDT %u != %u at %u[%u]\n",
+                              dindir_buf[gdt_off], gdt_blk,dindir_blk,gdt_off);
+#endif
+                       retval = EXT2_ET_RESIZE_INODE_CORRUPT;
+                       goto out_dindir;
+               }
+
+               while ((grp = list_backups(fs, &three, &five, &seven)) <
+                      fs->group_desc_count) {
+                       blk_t expect = gdt_blk + grp * sb->s_blocks_per_group;
+
+                       if (!gdt_buf[last]) {
+#ifdef RES_GDT_DEBUG
+                               printf("added backup GDT %u grp %u@%u[%u]\n",
+                                      expect, grp, gdt_blk, last);
+#endif
+                               gdt_buf[last] = expect;
+                               inode.i_blocks += rsv_add;
+                               gdt_dirty = inode_dirty = 1;
+                       } else if (gdt_buf[last] != expect) {
+#ifdef RES_GDT_DEBUG
+                               printf("bad backup GDT %u != %u at %u[%u]\n",
+                                      gdt_buf[last], expect, gdt_blk, last);
+#endif
+                               retval = EXT2_ET_RESIZE_INODE_CORRUPT;
+                               goto out_dindir;
+                       }
+                       last++;
+               }
+               if (gdt_dirty) {
+#ifdef RES_GDT_DEBUG
+                       printf("writing primary GDT block %u\n", gdt_blk);
+#endif
+                       retval = ext2fs_write_ind_block(fs, gdt_blk, gdt_buf);
+                       if (retval)
+                               goto out_dindir;
+               }
+       }
+
+out_dindir:
+       if (dindir_dirty) {
+               retval2 = ext2fs_write_ind_block(fs, dindir_blk, dindir_buf);
+               if (!retval)
+                       retval = retval2;
+       }
+out_inode:
+#ifdef RES_GDT_DEBUG
+       printf("inode.i_blocks = %u, i_size = %u\n", inode.i_blocks,
+              inode.i_size);
+#endif
+       if (inode_dirty) {
+               inode.i_atime = inode.i_mtime = time(0);
+               retval2 = ext2fs_write_inode(fs, EXT2_RESIZE_INO, &inode);
+               if (!retval)
+                       retval = retval2;
+       }
+out_free:
+       ext2fs_free_mem((void *)&dindir_buf);
+       return retval;
+}
+
diff --git a/e2fsprogs/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..bd74225
--- /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(value, "w");
+       if (!data->outfile)
+               data->outfile = stderr;
+
+       data->flags = 0;
+       if ((value = getenv("TEST_IO_FLAGS")) != NULL)
+               data->flags = strtoul(value, NULL, 0);
+
+       data->block = 0;
+       if ((value = getenv("TEST_IO_BLOCK")) != NULL)
+               data->block = strtoul(value, NULL, 0);
+
+       data->read_abort_count = 0;
+       if ((value = getenv("TEST_IO_READ_ABORT")) != NULL)
+               data->read_abort_count = strtoul(value, NULL, 0);
+
+       data->write_abort_count = 0;
+       if ((value = getenv("TEST_IO_WRITE_ABORT")) != NULL)
+               data->write_abort_count = strtoul(value, NULL, 0);
+
+       *channel = io;
+       return 0;
+
+cleanup:
+       ext2fs_free_mem(&io);
+       ext2fs_free_mem(&data);
+       return retval;
+}
+
+static errcode_t test_close(io_channel channel)
+{
+       struct test_private_data *data;
+       errcode_t       retval = 0;
+
+       EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+       data = (struct test_private_data *) channel->private_data;
+       EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL);
+
+       if (--channel->refcount > 0)
+               return 0;
+
+       if (data->real)
+               retval = io_channel_close(data->real);
+
+       if (data->outfile && data->outfile != stderr)
+               fclose(data->outfile);
+
+       ext2fs_free_mem(&channel->private_data);
+       ext2fs_free_mem(&channel->name);
+       ext2fs_free_mem(&channel);
+       return retval;
+}
+
+static errcode_t test_set_blksize(io_channel channel, int blksize)
+{
+       struct test_private_data *data;
+       errcode_t       retval = 0;
+
+       EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+       data = (struct test_private_data *) channel->private_data;
+       EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL);
+
+       if (data->real)
+               retval = io_channel_set_blksize(data->real, blksize);
+       if (data->set_blksize)
+               data->set_blksize(blksize, retval);
+       if (data->flags & TEST_FLAG_SET_BLKSIZE)
+               fprintf(data->outfile,
+                       "Test_io: set_blksize(%d) returned %s\n",
+                       blksize, retval ? error_message(retval) : "OK");
+       channel->block_size = blksize;
+       return retval;
+}
+
+
+static errcode_t test_read_blk(io_channel channel, unsigned long block,
+                              int count, void *buf)
+{
+       struct test_private_data *data;
+       errcode_t       retval = 0;
+
+       EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+       data = (struct test_private_data *) channel->private_data;
+       EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL);
+
+       if (data->real)
+               retval = io_channel_read_blk(data->real, block, count, buf);
+       if (data->read_blk)
+               data->read_blk(block, count, retval);
+       if (data->flags & TEST_FLAG_READ)
+               fprintf(data->outfile,
+                       "Test_io: read_blk(%lu, %d) returned %s\n",
+                       block, count, retval ? error_message(retval) : "OK");
+       if (data->block && data->block == block) {
+               if (data->flags & TEST_FLAG_DUMP)
+                       test_dump_block(channel, data, block, buf);
+               if (--data->read_abort_count == 0)
+                       test_abort(channel, block);
+       }
+       return retval;
+}
+
+static errcode_t test_write_blk(io_channel channel, unsigned long block,
+                              int count, const void *buf)
+{
+       struct test_private_data *data;
+       errcode_t       retval = 0;
+
+       EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+       data = (struct test_private_data *) channel->private_data;
+       EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL);
+
+       if (data->real)
+               retval = io_channel_write_blk(data->real, block, count, buf);
+       if (data->write_blk)
+               data->write_blk(block, count, retval);
+       if (data->flags & TEST_FLAG_WRITE)
+               fprintf(data->outfile,
+                       "Test_io: write_blk(%lu, %d) returned %s\n",
+                       block, count, retval ? error_message(retval) : "OK");
+       if (data->block && data->block == block) {
+               if (data->flags & TEST_FLAG_DUMP)
+                       test_dump_block(channel, data, block, buf);
+               if (--data->write_abort_count == 0)
+                       test_abort(channel, block);
+       }
+       return retval;
+}
+
+static errcode_t test_write_byte(io_channel channel, unsigned long offset,
+                              int count, const void *buf)
+{
+       struct test_private_data *data;
+       errcode_t       retval = 0;
+
+       EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+       data = (struct test_private_data *) channel->private_data;
+       EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL);
+
+       if (data->real && data->real->manager->write_byte)
+               retval = io_channel_write_byte(data->real, offset, count, buf);
+       if (data->write_byte)
+               data->write_byte(offset, count, retval);
+       if (data->flags & TEST_FLAG_WRITE)
+               fprintf(data->outfile,
+                       "Test_io: write_byte(%lu, %d) returned %s\n",
+                       offset, count, retval ? error_message(retval) : "OK");
+       return retval;
+}
+
+/*
+ * Flush data buffers to disk.
+ */
+static errcode_t test_flush(io_channel channel)
+{
+       struct test_private_data *data;
+       errcode_t       retval = 0;
+
+       EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+       data = (struct test_private_data *) channel->private_data;
+       EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL);
+
+       if (data->real)
+               retval = io_channel_flush(data->real);
+
+       if (data->flags & TEST_FLAG_FLUSH)
+               fprintf(data->outfile, "Test_io: flush() returned %s\n",
+                       retval ? error_message(retval) : "OK");
+
+       return retval;
+}
+
+static errcode_t test_set_option(io_channel channel, const char *option,
+                                const char *arg)
+{
+       struct test_private_data *data;
+       errcode_t       retval = 0;
+
+       EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+       data = (struct test_private_data *) channel->private_data;
+       EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL);
+
+
+       if (data->flags & TEST_FLAG_SET_OPTION)
+               fprintf(data->outfile, "Test_io: set_option(%s, %s) ",
+                       option, arg);
+       if (data->real && data->real->manager->set_option) {
+               retval = (data->real->manager->set_option)(data->real,
+                                                          option, arg);
+               if (data->flags & TEST_FLAG_SET_OPTION)
+                       fprintf(data->outfile, "returned %s\n",
+                               retval ? error_message(retval) : "OK");
+       } else {
+               if (data->flags & TEST_FLAG_SET_OPTION)
+                       fprintf(data->outfile, "not implemented\n");
+       }
+       return retval;
+}
diff --git a/e2fsprogs/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..a51c33a
--- /dev/null
@@ -0,0 +1,1391 @@
+/* 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.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#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 = strdup(s);
+       return ret;
+}
+
+static int string_to_int(const char *s)
+{
+       long l;
+       char *p;
+
+       l = strtol(s, &p, 0);
+       if (*p || l == LONG_MIN || l == LONG_MAX || l < 0 || l > INT_MAX)
+               return -1;
+       else
+               return (int) l;
+}
+
+static char *skip_over_blank(char *cp)
+{
+       while (*cp && isspace(*cp))
+               cp++;
+       return cp;
+}
+
+static char *skip_over_word(char *cp)
+{
+       while (*cp && !isspace(*cp))
+               cp++;
+       return cp;
+}
+
+static void strip_line(char *line)
+{
+       char    *p;
+
+       while (*line) {
+               p = line + strlen(line) - 1;
+               if ((*p == '\n') || (*p == '\r'))
+                       *p = 0;
+               else
+                       break;
+       }
+}
+
+static char *parse_word(char **buf)
+{
+       char *word, *next;
+
+       word = *buf;
+       if (*word == 0)
+               return 0;
+
+       word = skip_over_blank(word);
+       next = skip_over_word(word);
+       if (*next)
+               *next++ = 0;
+       *buf = next;
+       return word;
+}
+
+static void parse_escape(char *word)
+{
+       char    *q, c;
+       const char *p;
+
+       if (!word)
+               return;
+
+       for (p = q = word; *p; q++) {
+               c = *p++;
+               if (c != '\\') {
+                       *q = c;
+               } else {
+                       *q = bb_process_escape_sequence(&p);
+               }
+       }
+       *q = 0;
+}
+
+static void free_instance(struct fsck_instance *i)
+{
+       if (i->prog)
+               free(i->prog);
+       if (i->device)
+               free(i->device);
+       if (i->base_device)
+               free(i->base_device);
+       free(i);
+}
+
+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..277ec7c
--- /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_flags(stdout, fsflags, PFOPT_LONG);
+               bb_putchar('\n');
+       } else {
+               print_flags(stdout, fsflags, 0);
+               printf(" %s\n", name);
+       }
+
+       return;
+read_err:
+       bb_perror_msg("reading %s", name);
+}
+
+static int lsattr_dir_proc(const char *, struct dirent *, void *);
+
+static void lsattr_args(const char *name)
+{
+       struct stat st;
+
+       if (lstat(name, &st) == -1) {
+               bb_perror_msg("stating %s", name);
+       } else {
+               if (S_ISDIR(st.st_mode) && !(flags & OPT_DIRS_OPT))
+                       iterate_on_dir(name, lsattr_dir_proc, NULL);
+               else
+                       list_attributes(name);
+       }
+}
+
+static int lsattr_dir_proc(const char *dir_name, struct dirent *de,
+                          void *private)
+{
+       struct stat st;
+       char *path;
+
+       path = concat_path_file(dir_name, de->d_name);
+
+       if (lstat(path, &st) == -1)
+               bb_perror_msg(path);
+       else {
+               if (de->d_name[0] != '.' || (flags & OPT_ALL)) {
+                       list_attributes(path);
+                       if (S_ISDIR(st.st_mode) && (flags & OPT_RECUR) &&
+                          (de->d_name[0] != '.' && (de->d_name[1] != '\0' ||
+                          (de->d_name[1] != '.' && de->d_name[2] != '\0')))) {
+                               printf("\n%s:\n", path);
+                               iterate_on_dir(path, lsattr_dir_proc, NULL);
+                               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..89b5223
--- /dev/null
@@ -0,0 +1,1336 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mke2fs.c - Make a ext2fs filesystem.
+ *
+ * Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+ *     2003, 2004, 2005 by Theodore Ts'o.
+ *
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ */
+
+/* Usage: mke2fs [options] device
+ *
+ * The device may be a block device or a image of one, but this isn't
+ * enforced (but it's not much fun on a character device :-).
+ */
+
+#include <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(bad_blocks_file, "r");
+       retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block);
+       fclose (f);
+       mke2fs_error_msg_and_die(retval, "read bad blocks from list");
+}
+
+/*
+ * Runs the badblocks program to test the disk
+ */
+static void test_disk(ext2_filsys fs, badblocks_list *bb_list)
+{
+       FILE            *f;
+       errcode_t       retval;
+       char            buf[1024];
+
+       sprintf(buf, "badblocks -b %d %s%s%s %d", fs->blocksize,
+               quiet ? "" : "-s ", (cflag > 1) ? "-w " : "",
+               fs->device_name, fs->super->s_blocks_count);
+       mke2fs_verbose("Running command: %s\n", buf);
+       f = popen(buf, "r");
+       if (!f) {
+               bb_perror_msg_and_die("cannot run '%s'", buf);
+       }
+       retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block);
+       pclose(f);
+       mke2fs_error_msg_and_die(retval, "read bad blocks from program");
+}
+
+static void handle_bad_blocks(ext2_filsys fs, badblocks_list bb_list)
+{
+       dgrp_t                  i;
+       blk_t                   j;
+       unsigned                must_be_good;
+       blk_t                   blk;
+       badblocks_iterate       bb_iter;
+       errcode_t               retval;
+       blk_t                   group_block;
+       int                     group;
+       int                     group_bad;
+
+       if (!bb_list)
+               return;
+
+       /*
+        * The primary superblock and group descriptors *must* be
+        * good; if not, abort.
+        */
+       must_be_good = fs->super->s_first_data_block + 1 + fs->desc_blocks;
+       for (i = fs->super->s_first_data_block; i <= must_be_good; i++) {
+               if (ext2fs_badblocks_list_test(bb_list, i)) {
+                       bb_error_msg_and_die(
+                               "Block %d in primary superblock/group descriptor area bad\n"
+                               "Blocks %d through %d must be good in order to build a filesystem\n"
+                               "Aborting ...", i, fs->super->s_first_data_block, must_be_good);
+               }
+       }
+
+       /*
+        * See if any of the bad blocks are showing up in the backup
+        * superblocks and/or group descriptors.  If so, issue a
+        * warning and adjust the block counts appropriately.
+        */
+       group_block = fs->super->s_first_data_block +
+               fs->super->s_blocks_per_group;
+
+       for (i = 1; i < fs->group_desc_count; i++) {
+               group_bad = 0;
+               for (j=0; j < fs->desc_blocks+1; j++) {
+                       if (ext2fs_badblocks_list_test(bb_list,
+                                                      group_block + j)) {
+                               mke2fs_warning_msg(!group_bad,
+                                       "the backup superblock/group descriptors at block %d contain\n"
+                                       "bad blocks\n", group_block);
+                               group_bad++;
+                               group = ext2fs_group_of_blk(fs, group_block+j);
+                               fs->group_desc[group].bg_free_blocks_count++;
+                               fs->super->s_free_blocks_count++;
+                       }
+               }
+               group_block += fs->super->s_blocks_per_group;
+       }
+
+       /*
+        * Mark all the bad blocks as used...
+        */
+       retval = ext2fs_badblocks_list_iterate_begin(bb_list, &bb_iter);
+       mke2fs_error_msg_and_die(retval, "mark bad blocks as used");
+
+       while (ext2fs_badblocks_list_iterate(bb_iter, &blk))
+               ext2fs_mark_block_bitmap(fs->block_map, blk);
+       ext2fs_badblocks_list_iterate_end(bb_iter);
+}
+
+/*
+ * These functions implement a generalized progress meter.
+ */
+struct progress_struct {
+       char            format[20];
+       char            backup[80];
+       __u32           max;
+       int             skip_progress;
+};
+
+static void progress_init(struct progress_struct *progress,
+                         const char *label,__u32 max)
+{
+       int     i;
+
+       memset(progress, 0, sizeof(struct progress_struct));
+       if (quiet)
+               return;
+
+       /*
+        * Figure out how many digits we need
+        */
+       i = int_log10(max);
+       sprintf(progress->format, "%%%dd/%%%dld", i, i);
+       memset(progress->backup, '\b', sizeof(progress->backup)-1);
+       progress->backup[sizeof(progress->backup)-1] = 0;
+       if ((2*i)+1 < (int) sizeof(progress->backup))
+               progress->backup[(2*i)+1] = 0;
+       progress->max = max;
+
+       progress->skip_progress = 0;
+       if (getenv("MKE2FS_SKIP_PROGRESS"))
+               progress->skip_progress++;
+
+       fputs(label, stdout);
+       fflush(stdout);
+}
+
+static void progress_update(struct progress_struct *progress, __u32 val)
+{
+       if ((progress->format[0] == 0) || progress->skip_progress)
+               return;
+       printf(progress->format, val, progress->max);
+       fputs(progress->backup, stdout);
+}
+
+static void progress_close(struct progress_struct *progress)
+{
+       if (progress->format[0] == 0)
+               return;
+       printf("%-28s\n", "done");
+}
+
+
+/*
+ * Helper function which zeros out _num_ blocks starting at _blk_.  In
+ * case of an error, the details of the error is returned via _ret_blk_
+ * and _ret_count_ if they are non-NULL pointers.  Returns 0 on
+ * success, and an error code on an error.
+ *
+ * As a special case, if the first argument is NULL, then it will
+ * attempt to free the static zeroizing buffer.  (This is to keep
+ * programs that check for memory leaks happy.)
+ */
+static errcode_t zero_blocks(ext2_filsys fs, blk_t blk, int num,
+                            struct progress_struct *progress,
+                            blk_t *ret_blk, int *ret_count)
+{
+       int             j, count, next_update, next_update_incr;
+       static char     *buf;
+       errcode_t       retval;
+
+       /* If fs is null, clean up the static buffer and return */
+       if (!fs) {
+               if (buf) {
+                       free(buf);
+                       buf = 0;
+               }
+               return 0;
+       }
+       /* Allocate the zeroizing buffer if necessary */
+       if (!buf) {
+               buf = xzalloc(fs->blocksize * STRIDE_LENGTH);
+       }
+       /* OK, do the write loop */
+       next_update = 0;
+       next_update_incr = num / 100;
+       if (next_update_incr < 1)
+               next_update_incr = 1;
+       for (j=0; j < num; j += STRIDE_LENGTH, blk += STRIDE_LENGTH) {
+               count = num - j;
+               if (count > STRIDE_LENGTH)
+                       count = STRIDE_LENGTH;
+               retval = io_channel_write_blk(fs->io, blk, count, buf);
+               if (retval) {
+                       if (ret_count)
+                               *ret_count = count;
+                       if (ret_blk)
+                               *ret_blk = blk;
+                       return retval;
+               }
+               if (progress && j > next_update) {
+                       next_update += num / 100;
+                       progress_update(progress, blk);
+               }
+       }
+       return 0;
+}
+
+static void write_inode_tables(ext2_filsys fs)
+{
+       errcode_t       retval;
+       blk_t           blk;
+       dgrp_t          i;
+       int             num;
+       struct progress_struct progress;
+
+       if (quiet)
+               memset(&progress, 0, sizeof(progress));
+       else
+               progress_init(&progress, "Writing inode tables: ",
+                             fs->group_desc_count);
+
+       for (i = 0; i < fs->group_desc_count; i++) {
+               progress_update(&progress, i);
+
+               blk = fs->group_desc[i].bg_inode_table;
+               num = fs->inode_blocks_per_group;
+
+               retval = zero_blocks(fs, blk, num, 0, &blk, &num);
+               mke2fs_error_msg_and_die(retval,
+                       "write %d blocks in inode table starting at %d.",
+                       num, blk);
+               if (sync_kludge) {
+                       if (sync_kludge == 1)
+                               sync();
+                       else if ((i % sync_kludge) == 0)
+                               sync();
+               }
+       }
+       zero_blocks(0, 0, 0, 0, 0, 0);
+       progress_close(&progress);
+}
+
+static void create_root_dir(ext2_filsys fs)
+{
+       errcode_t               retval;
+       struct ext2_inode       inode;
+
+       retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, 0);
+       mke2fs_error_msg_and_die(retval, "create root dir");
+       if (geteuid()) {
+               retval = ext2fs_read_inode(fs, EXT2_ROOT_INO, &inode);
+               mke2fs_error_msg_and_die(retval, "read root inode");
+               inode.i_uid = getuid();
+               if (inode.i_uid)
+                       inode.i_gid = getgid();
+               retval = ext2fs_write_new_inode(fs, EXT2_ROOT_INO, &inode);
+               mke2fs_error_msg_and_die(retval, "set root inode ownership");
+       }
+}
+
+static void create_lost_and_found(ext2_filsys fs)
+{
+       errcode_t               retval;
+       ext2_ino_t              ino;
+       const char              *name = "lost+found";
+       int                     i = 1;
+       char                    *msg = "create";
+       int                     lpf_size = 0;
+
+       fs->umask = 077;
+       retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, 0, name);
+       if (retval) {
+               goto CREATE_LOST_AND_FOUND_ERROR;
+       }
+
+       retval = ext2fs_lookup(fs, EXT2_ROOT_INO, name, strlen(name), 0, &ino);
+       if (retval) {
+               msg = "lookup";
+               goto CREATE_LOST_AND_FOUND_ERROR;
+       }
+
+       for (; i < EXT2_NDIR_BLOCKS; i++) {
+               if ((lpf_size += fs->blocksize) >= 16*1024)
+                       break;
+               retval = ext2fs_expand_dir(fs, ino);
+               msg = "expand";
+CREATE_LOST_AND_FOUND_ERROR:
+               mke2fs_error_msg_and_die(retval, "%s %s", msg, name);
+       }
+}
+
+static void create_bad_block_inode(ext2_filsys fs, badblocks_list bb_list)
+{
+       errcode_t       retval;
+
+       ext2fs_mark_inode_bitmap(fs->inode_map, EXT2_BAD_INO);
+       fs->group_desc[0].bg_free_inodes_count--;
+       fs->super->s_free_inodes_count--;
+       retval = ext2fs_update_bb_inode(fs, bb_list);
+       mke2fs_error_msg_and_die(retval, "set bad block inode");
+}
+
+static void reserve_inodes(ext2_filsys fs)
+{
+       ext2_ino_t      i;
+       int             group;
+
+       for (i = EXT2_ROOT_INO + 1; i < EXT2_FIRST_INODE(fs->super); i++) {
+               ext2fs_mark_inode_bitmap(fs->inode_map, i);
+               group = ext2fs_group_of_ino(fs, i);
+               fs->group_desc[group].bg_free_inodes_count--;
+               fs->super->s_free_inodes_count--;
+       }
+       ext2fs_mark_ib_dirty(fs);
+}
+
+#define BSD_DISKMAGIC   (0x82564557UL)  /* The disk magic number */
+#define BSD_MAGICDISK   (0x57455682UL)  /* The disk magic number reversed */
+#define BSD_LABEL_OFFSET        64
+
+static void zap_sector(ext2_filsys fs, int sect, int nsect)
+{
+       char *buf;
+       char *fmt = "could not %s %d";
+       int retval;
+       unsigned int *magic;
+
+       buf = xmalloc(512*nsect);
+
+       if (sect == 0) {
+               /* Check for a BSD disklabel, and don't erase it if so */
+               retval = io_channel_read_blk(fs->io, 0, -512, buf);
+               if (retval)
+                       mke2fs_warning_msg(retval, fmt, "read block", 0);
+               else {
+                       magic = (unsigned int *) (buf + BSD_LABEL_OFFSET);
+                       if ((*magic == BSD_DISKMAGIC) ||
+                           (*magic == BSD_MAGICDISK))
+                               return;
+               }
+       }
+
+       memset(buf, 0, 512*nsect);
+       io_channel_set_blksize(fs->io, 512);
+       retval = io_channel_write_blk(fs->io, sect, -512*nsect, buf);
+       io_channel_set_blksize(fs->io, fs->blocksize);
+       free(buf);
+       mke2fs_warning_msg(retval, fmt, "erase sector", sect);
+}
+
+static void create_journal_dev(ext2_filsys fs)
+{
+       struct progress_struct  progress;
+       errcode_t               retval;
+       char                    *buf;
+       char                    *fmt = "%s journal superblock";
+       blk_t                   blk;
+       int                     count;
+
+       retval = ext2fs_create_journal_superblock(fs,
+                                 fs->super->s_blocks_count, 0, &buf);
+       mke2fs_error_msg_and_die(retval, fmt, "init");
+       if (quiet)
+               memset(&progress, 0, sizeof(progress));
+       else
+               progress_init(&progress, "Zeroing journal device: ",
+                             fs->super->s_blocks_count);
+
+       retval = zero_blocks(fs, 0, fs->super->s_blocks_count,
+                            &progress, &blk, &count);
+       mke2fs_error_msg_and_die(retval, "zero journal device (block %u, count %d)",
+                       blk, count);
+       zero_blocks(0, 0, 0, 0, 0, 0);
+
+       retval = io_channel_write_blk(fs->io,
+                                     fs->super->s_first_data_block+1,
+                                     1, buf);
+       mke2fs_error_msg_and_die(retval, fmt, "write");
+       progress_close(&progress);
+}
+
+static void show_stats(ext2_filsys fs)
+{
+       struct ext2_super_block *s = fs->super;
+       char                    *os;
+       blk_t                   group_block;
+       dgrp_t                  i;
+       int                     need, col_left;
+
+       mke2fs_warning_msg((param.s_blocks_count != s->s_blocks_count),
+               "%d blocks unused\n", param.s_blocks_count - s->s_blocks_count);
+       os = e2p_os2string(fs->super->s_creator_os);
+       printf( "Filesystem label=%.*s\n"
+                       "OS type: %s\n"
+                       "Block size=%u (log=%u)\n"
+                       "Fragment size=%u (log=%u)\n"
+                       "%u inodes, %u blocks\n"
+                       "%u blocks (%2.2f%%) reserved for the super user\n"
+                       "First data block=%u\n",
+                       (int) sizeof(s->s_volume_name),
+                       s->s_volume_name,
+                       os,
+                       fs->blocksize, s->s_log_block_size,
+                       fs->fragsize, s->s_log_frag_size,
+                       s->s_inodes_count, s->s_blocks_count,
+                       s->s_r_blocks_count, 100.0 * s->s_r_blocks_count / s->s_blocks_count,
+                       s->s_first_data_block);
+       free(os);
+       if (s->s_reserved_gdt_blocks) {
+               printf("Maximum filesystem blocks=%lu\n",
+                      (s->s_reserved_gdt_blocks + fs->desc_blocks) *
+                      (fs->blocksize / sizeof(struct ext2_group_desc)) *
+                      s->s_blocks_per_group);
+       }
+       printf( "%u block group%s\n"
+                       "%u blocks per group, %u fragments per group\n"
+                       "%u inodes per group\n",
+                       fs->group_desc_count, (fs->group_desc_count > 1) ? "s" : "",
+                       s->s_blocks_per_group, s->s_frags_per_group,
+                       s->s_inodes_per_group);
+       if (fs->group_desc_count == 1) {
+               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..b7a1b21
--- /dev/null
@@ -0,0 +1,713 @@
+/* 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.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/*
+ * History:
+ * 93/06/01    - Creation
+ * 93/10/31    - Added the -c option to change the maximal mount counts
+ * 93/12/14    - Added -l flag to list contents of superblock
+ *                M.J.E. Mol (marcel@duteca.et.tudelft.nl)
+ *                F.W. ten Wolde (franky@duteca.et.tudelft.nl)
+ * 93/12/29    - Added the -e option to change errors behavior
+ * 94/02/27    - Ported to use the ext2fs library
+ * 94/03/06    - Added the checks interval from Uwe Ohse (uwe@tirka.gun.de)
+ */
+
+#include <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..b30c294
--- /dev/null
@@ -0,0 +1,267 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * util.c --- helper functions used by tune2fs and mke2fs
+ *
+ * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include <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..03a9f37
--- /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(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ);
+#ifdef SIOCGIFHWADDR
+               if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0)
+                       continue;
+               a = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
+#else
+#ifdef SIOCGENADDR
+               if (ioctl(sd, SIOCGENADDR, &ifr) < 0)
+                       continue;
+               a = (unsigned char *) ifr.ifr_enaddr;
+#else
+#ifdef HAVE_NET_IF_DL_H
+               sdlp = (struct sockaddr_dl *) &ifrp->ifr_addr;
+               if ((sdlp->sdl_family != AF_LINK) || (sdlp->sdl_alen != 6))
+                       continue;
+               a = (unsigned char *) &sdlp->sdl_data[sdlp->sdl_nlen];
+#else
+               /*
+                * XXX we don't have a way of getting the hardware
+                * address
+                */
+               close(sd);
+               return 0;
+#endif /* HAVE_NET_IF_DL_H */
+#endif /* SIOCGENADDR */
+#endif /* SIOCGIFHWADDR */
+               if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5])
+                       continue;
+               if (node_id) {
+                       memcpy(node_id, a, 6);
+                       close(sd);
+                       return 1;
+               }
+       }
+       close(sd);
+#endif
+       return 0;
+}
+
+/* Assume that the gettimeofday() has microsecond granularity */
+#define MAX_ADJUSTMENT 10
+
+static int get_clock(uint32_t *clock_high, uint32_t *clock_low, uint16_t *ret_clock_seq)
+{
+       static int                      adjustment = 0;
+       static struct timeval           last = {0, 0};
+       static uint16_t                 clock_seq;
+       struct timeval                  tv;
+       unsigned long long              clock_reg;
+
+try_again:
+       gettimeofday(&tv, 0);
+       if ((last.tv_sec == 0) && (last.tv_usec == 0)) {
+               get_random_bytes(&clock_seq, sizeof(clock_seq));
+               clock_seq &= 0x3FFF;
+               last = tv;
+               last.tv_sec--;
+       }
+       if ((tv.tv_sec < last.tv_sec) ||
+           ((tv.tv_sec == last.tv_sec) &&
+            (tv.tv_usec < last.tv_usec))) {
+               clock_seq = (clock_seq+1) & 0x3FFF;
+               adjustment = 0;
+               last = tv;
+       } else if ((tv.tv_sec == last.tv_sec) &&
+           (tv.tv_usec == last.tv_usec)) {
+               if (adjustment >= MAX_ADJUSTMENT)
+                       goto try_again;
+               adjustment++;
+       } else {
+               adjustment = 0;
+               last = tv;
+       }
+
+       clock_reg = tv.tv_usec*10 + adjustment;
+       clock_reg += ((unsigned long long) tv.tv_sec)*10000000;
+       clock_reg += (((unsigned long long) 0x01B21DD2) << 32) + 0x13814000;
+
+       *clock_high = clock_reg >> 32;
+       *clock_low = clock_reg;
+       *ret_clock_seq = clock_seq;
+       return 0;
+}
+
+void uuid_generate_time(uuid_t out)
+{
+       static unsigned char node_id[6];
+       static int has_init = 0;
+       struct uuid uu;
+       uint32_t        clock_mid;
+
+       if (!has_init) {
+               if (get_node_id(node_id) <= 0) {
+                       get_random_bytes(node_id, 6);
+                       /*
+                        * Set multicast bit, to prevent conflicts
+                        * with IEEE 802 addresses obtained from
+                        * network cards
+                        */
+                       node_id[0] |= 0x01;
+               }
+               has_init = 1;
+       }
+       get_clock(&clock_mid, &uu.time_low, &uu.clock_seq);
+       uu.clock_seq |= 0x8000;
+       uu.time_mid = (uint16_t) clock_mid;
+       uu.time_hi_and_version = ((clock_mid >> 16) & 0x0FFF) | 0x1000;
+       memcpy(uu.node, node_id, 6);
+       uuid_pack(&uu, out);
+}
+
+void uuid_generate_random(uuid_t out)
+{
+       uuid_t  buf;
+       struct uuid uu;
+
+       get_random_bytes(buf, sizeof(buf));
+       uuid_unpack(buf, &uu);
+
+       uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;
+       uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000;
+       uuid_pack(&uu, out);
+}
+
+/*
+ * This is the generic front-end to uuid_generate_random and
+ * uuid_generate_time.  It uses uuid_generate_random only if
+ * /dev/urandom is available, since otherwise we won't have
+ * high-quality randomness.
+ */
+void uuid_generate(uuid_t out)
+{
+       if (get_random_fd() >= 0)
+               uuid_generate_random(out);
+       else
+               uuid_generate_time(out);
+}
diff --git a/e2fsprogs/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..bd53b15
--- /dev/null
@@ -0,0 +1,104 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Public include file for the UUID library
+ *
+ * Copyright (C) 1996, 1997, 1998 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifndef _UUID_UUID_H
+#define _UUID_UUID_H
+
+#include <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 ATTRIBUTE_UNUSED = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15}
+#else
+#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \
+       static const uuid_t name = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15}
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* clear.c */
+/*void uuid_clear(uuid_t uu);*/
+#define uuid_clear(uu) memset(uu, 0, sizeof(uu))
+
+/* compare.c */
+int uuid_compare(const uuid_t uu1, const uuid_t uu2);
+
+/* copy.c */
+/*void uuid_copy(uuid_t dst, const uuid_t src);*/
+#define uuid_copy(dst,src) memcpy(dst, src, sizeof(dst))
+
+/* gen_uuid.c */
+void uuid_generate(uuid_t out);
+void uuid_generate_random(uuid_t out);
+void uuid_generate_time(uuid_t out);
+
+/* isnull.c */
+/*int uuid_is_null(const uuid_t uu);*/
+#define uuid_is_null(uu) (!memcmp(uu, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sizeof(uu)))
+
+/* parse.c */
+int uuid_parse(const char *in, uuid_t uu);
+
+/* unparse.c */
+void uuid_unparse(const uuid_t uu, char *out);
+void uuid_unparse_lower(const uuid_t uu, char *out);
+void uuid_unparse_upper(const uuid_t uu, char *out);
+
+/* uuid_time.c */
+time_t uuid_time(const uuid_t uu, struct timeval *ret_tv);
+int uuid_type(const uuid_t uu);
+int uuid_variant(const uuid_t uu);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UUID_UUID_H */
diff --git a/e2fsprogs/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..58959aa
--- /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_MATH
+       bool "Enable math functions (requires libm)"
+       default y
+       depends on AWK
+       help
+         Enable math functions of the Awk programming language.
+         NOTE: This will require libm to be present for linking.
+
+config 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..f04ea5c
--- /dev/null
@@ -0,0 +1,2895 @@
+/* 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,     ARGIND,     ARGC,       ARGV,
+       ERRNO,      FNR,
+       NR,         NF,         IGNORECASE,
+       ENVIRON,    F0,         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"  "ARGIND\0"  "ARGC\0"    "ARGV\0"
+       "ERRNO\0"   "FNR\0"
+       "NR\0"      "NF\0*"     "IGNORECASE\0*"
+       "ENVIRON\0" "$\0*"      "\0";
+
+static const char vValues[] ALIGN1 =
+       "%.6g\0"    "%.6g\0"    " \0"       " \0"
+       "\n\0"      "\n\0"      "\0"        "\0"
+       "\034\0"
+       "\377";
+
+/* hash size may grow to these values */
+#define FIRST_PRIME 61
+static const 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 */
+struct globals {
+       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 */
+       double t_double;
+       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)]; - 0x6c */
+/* char Gsize[sizeof(G)]; - 0x1cc */
+/* Trying to keep most of members accessible with short offsets: */
+/* char Gofs_seed[offsetof(struct globals2, evaluate__seed)]; - 0x90 */
+#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_double     (G.t_double    )
+#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) ATTRIBUTE_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_MATH
+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) ATTRIBUTE_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(hash_item) + l);
+               memcpy(hi->name, name, l);
+
+               idx = hashidx(name) % hash->csize;
+               hi->next = hash->items[idx];
+               hash->items[idx] = hi;
+               hash->glen += l;
+       }
+       return &(hi->data);
+}
+
+#define findvar(hash, name) ((var*)    hash_find((hash), (name)))
+#define newvar(name)        ((var*)    hash_find(vhash, (name)))
+#define newfile(name)       ((rstream*)hash_find(fdhash, (name)))
+#define newfunc(name)       ((func*)   hash_find(fnhash, (name)))
+
+static void hash_remove(xhash *hash, const char *name)
+{
+       hash_item *hi, **phi;
+
+       phi = &(hash->items[hashidx(name) % hash->csize]);
+       while (*phi) {
+               hi = *phi;
+               if (strcmp(hi->name, name) == 0) {
+                       hash->glen -= (strlen(name) + 1);
+                       hash->nel--;
+                       *phi = hi->next;
+                       free(hi);
+                       break;
+               }
+               phi = &(hi->next);
+       }
+}
+
+/* ------ some useful functions ------ */
+
+static void skip_spaces(char **s)
+{
+       char *p = *s;
+
+       while (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 int ALWAYS_INLINE isalnum_(int c)
+{
+       return (isalnum(c) || c == '_');
+}
+
+static FILE *afopen(const char *path, const char *mode)
+{
+       return (*path == '-' && *(path+1) == '\0') ? stdin : xfopen(path, mode);
+}
+
+/* -------- working with variables (set/get/copy/etc) -------- */
+
+static xhash *iamarray(var *v)
+{
+       var *a = v;
+
+       while (a->type & VF_CHILD)
+               a = a->x.parent;
+
+       if (!(a->type & VF_ARRAY)) {
+               a->type |= VF_ARRAY;
+               a->x.array = hash_init();
+       }
+       return a->x.array;
+}
+
+static void clear_array(xhash *array)
+{
+       unsigned 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 = strtod(s, &s);
+                       if (v->type & VF_USER) {
+                               skip_spaces(&s);
+                               if (*s != '\0')
+                                       v->type &= ~VF_USER;
+                       }
+               } else {
+                       v->type &= ~VF_USER;
+               }
+               v->type |= VF_CACHED;
+       }
+       return v->number;
+}
+
+static var *copyvar(var *dest, const var *src)
+{
+       if (dest != src) {
+               clrvar(dest);
+               dest->type |= (src->type & ~(VF_DONTTOUCH | 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 = xmalloc(sizeof(nvblock) + size * sizeof(var));
+               g_cb->size = size;
+               g_cb->pos = g_cb->nv;
+               g_cb->prev = pb;
+               g_cb->next = NULL;
+               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 = strtod(p, &p);
+                       if (*p == '.')
+                               syntax_error(EMSG_UNEXP_TOKEN);
+                       tc = TC_NUMBER;
+
+               } else {
+                       /* search for something known */
+                       tl = tokenlist;
+                       tc = 0x00000001;
+                       ti = tokeninfo;
+                       while (*tl) {
+                               l = *(tl++);
+                               if (l == NTCC) {
+                                       tc <<= 1;
+                                       continue;
+                               }
+                               /* if token class is expected, token
+                                * matches and it's not a longer word,
+                                * then this is what we are looking for
+                                */
+                               if ((tc & (expected | TC_WORD | TC_NEWLINE))
+                                && *tl == *p && strncmp(p, tl, l) == 0
+                                && !((tc & TC_WORD) && isalnum_(p[l]))
+                               ) {
+                                       t_info = *ti;
+                                       p += l;
+                                       break;
+                               }
+                               ti++;
+                               tl += l;
+                       }
+
+                       if (!*tl) {
+                               /* it's a name (var/array/function),
+                                * otherwise it's something wrong
+                                */
+                               if (!isalnum_(*p))
+                                       syntax_error(EMSG_UNEXP_TOKEN);
+
+                               t_string = --p;
+                               while (isalnum_(*(++p))) {
+                                       *(p-1) = *p;
+                               }
+                               *(p-1) = '\0';
+                               tc = TC_VARIABLE;
+                               /* also consume whitespace between functionname and bracket */
+                               if (!(expected & TC_VARIABLE))
+                                       skip_spaces(&p);
+                               if (*p == '(') {
+                                       tc = TC_FUNCTION;
+                               } else {
+                                       if (*p == '[') {
+                                               p++;
+                                               tc = TC_ARRAY;
+                                       }
+                               }
+                       }
+               }
+               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)
+{
+       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));
+       xregcomp(preg, s, icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED);
+       nvfree(v);
+       return preg;
+}
+
+/* gradually increasing buffer */
+static void qrealloc(char **b, int n, int *size)
+{
+       if (!*b || n >= *size)
+               *b = xrealloc(*b, *size = n + (n>>1) + 80);
+}
+
+/* resize field storage space */
+static void fsrealloc(int size)
+{
+       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);
+                       s1[l] = '\0';
+                       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;
+       int 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 (nargs < (info >> 30))
+               syntax_error(EMSG_TOO_FEW_ARGS);
+
+       switch (info & OPNMASK) {
+
+       case B_a2:
+#if ENABLE_FEATURE_AWK_MATH
+               setvar_i(res, atan2(getvar_i(av[i]), 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 = xmalloc(n+1);
+               strncpy(s, as[0]+i, n);
+               s[n] = '\0';
+               setvar_p(res, s);
+               break;
+
+       case B_an:
+               setvar_i(res, (long)getvar_i(av[0]) & (long)getvar_i(av[1]));
+               break;
+
+       case B_co:
+               setvar_i(res, ~(long)getvar_i(av[0]));
+               break;
+
+       case B_ls:
+               setvar_i(res, (long)getvar_i(av[0]) << (long)getvar_i(av[1]));
+               break;
+
+       case B_or:
+               setvar_i(res, (long)getvar_i(av[0]) | (long)getvar_i(av[1]));
+               break;
+
+       case B_rs:
+               setvar_i(res, (long)((unsigned long)getvar_i(av[0]) >> (unsigned long)getvar_i(av[1])));
+               break;
+
+       case B_xo:
+               setvar_i(res, (long)getvar_i(av[0]) ^ (long)getvar_i(av[1]));
+               break;
+
+       case B_lo:
+               to_xxx = tolower;
+               goto lo_cont;
+
+       case B_up:
+               to_xxx = toupper;
+ lo_cont:
+               s1 = s = xstrdup(as[0]);
+               while (*s1) {
+                       *s1 = (*to_xxx)(*s1);
+                       s1++;
+               }
+               setvar_p(res, s);
+               break;
+
+       case B_ix:
+               n = 0;
+               ll = strlen(as[1]);
+               l = strlen(as[0]) - ll;
+               if (ll > 0 && l >= 0) {
+                       if (!icase) {
+                               s = strstr(as[0], as[1]);
+                               if (s) n = (s - as[0]) + 1;
+                       } else {
+                               /* this piece of code is terribly slow and
+                                * really should be rewritten
+                                */
+                               for (i=0; i<=l; i++) {
+                                       if (strncasecmp(as[0]+i, as[1], ll) == 0) {
+                                               n = i+1;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               setvar_i(res, n);
+               break;
+
+       case B_ti:
+               if (nargs > 1)
+                       tt = getvar_i(av[1]);
+               else
+                       time(&tt);
+               //s = (nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y";
+               i = strftime(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(L.s, "r");             /* 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_MATH
+                       case F_co:
+                               R.d = cos(L.d);
+                               break;
+
+                       case F_ex:
+                               R.d = exp(L.d);
+                               break;
+
+                       case F_lg:
+                               R.d = log(L.d);
+                               break;
+
+                       case F_si:
+                               R.d = sin(L.d);
+                               break;
+
+                       case F_sq:
+                               R.d = sqrt(L.d);
+                               break;
+#else
+                       case F_co:
+                       case F_ex:
+                       case F_lg:
+                       case F_si:
+                       case F_sq:
+                               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_MATH
+                               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 = afopen(fname, "r");
+               }
+       } 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 *opt_v = NULL;
+       int i, j, flen;
+       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::";
+       opt = getopt32(argv, "F:v:f:W:", &opt_F, &opt_v, &g_progname, &opt_W);
+       argv += optind;
+       argc -= optind;
+       if (opt & 0x1)
+               setvar_s(intvar[FS], opt_F); // -F
+       while (opt_v) { /* -v */
+               if (!is_assignment(llist_pop(&opt_v)))
+                       bb_show_usage();
+       }
+       if (opt & 0x4) { // -f
+               char *s = s; /* die, gcc, die */
+               FILE *from_file = afopen(g_progname, "r");
+               /* one byte is reserved for some trick in next_token */
+               if (fseek(from_file, 0, SEEK_END) == 0) {
+                       flen = ftell(from_file);
+                       s = xmalloc(flen + 4);
+                       fseek(from_file, 0, SEEK_SET);
+                       i = 1 + fread(s + 1, 1, flen, from_file);
+               } else {
+                       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);
+       } else { // no -f: take program from 1st parameter
+               if (!argc)
+                       bb_show_usage();
+               g_progname = "cmd. line";
+               parse_program(*argv++);
+               argc--;
+       }
+       if (opt & 0x8) // -W
+               bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W);
+
+       /* fill in ARGV array */
+       setvar_i(intvar[ARGC], argc + 1);
+       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..b211adf
--- /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 ATTRIBUTE_UNUSED, 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..4860778
--- /dev/null
@@ -0,0 +1,1278 @@
+/* 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
+
+/*
+ * Output flags
+ */
+#define D_HEADER        1      /* Print a header/footer between files */
+#define D_EMPTY1        2      /* Treat first file as empty (/dev/null) */
+#define D_EMPTY2        4      /* Treat second file as empty (/dev/null) */
+
+/*
+ * Status values for print_status() and diffreg() return values
+ * Guide:
+ * D_SAME - files are the same
+ * D_DIFFER - files differ
+ * D_BINARY - binary files differ
+ * D_COMMON - subdirectory common to both dirs
+ * D_ONLY - file only exists in one dir
+ * D_MISMATCH1 - path1 a dir, path2 a file
+ * D_MISMATCH2 - path1 a file, path2 a dir
+ * D_ERROR - error occurred
+ * D_SKIPPED1 - skipped path1 as it is a special file
+ * D_SKIPPED2 - skipped path2 as it is a special file
+ */
+
+#define D_SAME         0
+#define D_DIFFER       (1<<0)
+#define D_BINARY       (1<<1)
+#define D_COMMON       (1<<2)
+#define D_ONLY         (1<<3)
+#define D_MISMATCH1    (1<<4)
+#define D_MISMATCH2    (1<<5)
+#define D_ERROR                (1<<6)
+#define D_SKIPPED1     (1<<7)
+#define D_SKIPPED2     (1<<8)
+
+/* Command line options */
+#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)
+
+#define g_read_buf bb_common_bufsiz1
+
+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 */
+};
+
+struct globals {
+       USE_FEATURE_DIFF_DIR(char **dl;)
+       USE_FEATURE_DIFF_DIR(int dl_count;)
+       /* This is the default number of lines of context. */
+       int context;
+       size_t max_context;
+       int status;
+       char *start;
+       const char *label1;
+       const char *label2;
+       struct line *file[2];
+       int *J;          /* will be overlaid on class */
+       int *class;      /* will be overlaid on file[0] */
+       int *klist;      /* will be overlaid on file[0] after class */
+       int *member;     /* will be overlaid on file[1] */
+       int clen;
+       int len[2];
+       int pref, suff;  /* length of prefix and suffix */
+       int slen[2];
+       bool anychange;
+       long *ixnew;     /* will be overlaid on file[1] */
+       long *ixold;     /* will be overlaid on klist */
+       struct cand *clist;  /* merely a free storage pot for candidates */
+       int clistlen;    /* the length of clist */
+       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;
+       struct stat stb1, stb2;
+};
+#define G (*ptr_to_globals)
+#define dl                 (G.dl                )
+#define dl_count           (G.dl_count          )
+#define context            (G.context           )
+#define max_context        (G.max_context       )
+#define status             (G.status            )
+#define start              (G.start             )
+#define label1             (G.label1            )
+#define label2             (G.label2            )
+#define file               (G.file              )
+#define J                  (G.J                 )
+#define class              (G.class             )
+#define klist              (G.klist             )
+#define member             (G.member            )
+#define clen               (G.clen              )
+#define len                (G.len               )
+#define pref               (G.pref              )
+#define suff               (G.suff              )
+#define slen               (G.slen              )
+#define anychange          (G.anychange         )
+#define ixnew              (G.ixnew             )
+#define ixold              (G.ixold             )
+#define clist              (G.clist             )
+#define clistlen           (G.clistlen          )
+#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 INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       context = 3; \
+       max_context = 64; \
+} while (0)
+
+
+static void print_only(const char *path, size_t dirlen, const char *entry)
+{
+       if (dirlen > 1)
+               dirlen--;
+       printf("Only in %.*s: %s\n", (int) dirlen, path, entry);
+}
+
+static void print_status(int val, char *path1, char *path2, char *entry)
+{
+       const char *const _entry = entry ? entry : "";
+       char * 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, strlen(path1), entry);
+               break;
+       case D_COMMON:
+               printf("Common subdirectories: %s and %s\n", _path1, _path2);
+               break;
+       case D_BINARY:
+               printf("Binary files %s and %s differ\n", _path1, _path2);
+               break;
+       case D_DIFFER:
+               if (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_MISMATCH1:
+               printf("File %s is a %s while file %s is a %s\n",
+                          _path1, "directory", _path2, "regular file");
+               break;
+       case D_MISMATCH2:
+               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);
+       }
+}
+static ALWAYS_INLINE int fiddle_sum(int sum, int t)
+{
+       return sum * 127 + t;
+}
+/*
+ * Hash function taken from Robert Sedgewick, Algorithms in C, 3d ed., p 578.
+ */
+static int readhash(FILE *fp)
+{
+       int i, t, space;
+       int sum;
+
+       sum = 1;
+       space = 0;
+       if (!(option_mask32 & (FLAG_b | FLAG_w))) {
+               for (i = 0; (t = getc(fp)) != '\n'; i++) {
+                       if (t == EOF) {
+                               if (i == 0)
+                                       return 0;
+                               break;
+                       }
+                       sum = fiddle_sum(sum, t);
+               }
+       } else {
+               for (i = 0;;) {
+                       switch (t = getc(fp)) {
+                       case '\t':
+                       case '\r':
+                       case '\v':
+                       case '\f':
+                       case ' ':
+                               space++;
+                               continue;
+                       default:
+                               if (space && !(option_mask32 & FLAG_w)) {
+                                       i++;
+                                       space = 0;
+                               }
+                               sum = fiddle_sum(sum, t);
+                               i++;
+                               continue;
+                       case EOF:
+                               if (i == 0)
+                                       return 0;
+                               /* FALLTHROUGH */
+                       case '\n':
+                               break;
+                       }
+                       break;
+               }
+       }
+       /*
+        * There is a remote possibility that we end up with a zero sum.
+        * Zero is used as an EOF marker, so return 1 instead.
+        */
+       return (sum == 0 ? 1 : sum);
+}
+
+
+/*
+ * Check to see if the given files differ.
+ * Returns 0 if they are the same, 1 if different, and -1 on error.
+ */
+static int files_differ(FILE *f1, FILE *f2, int flags)
+{
+       size_t i, j;
+
+       if ((flags & (D_EMPTY1 | D_EMPTY2)) || stb1.st_size != stb2.st_size
+        || (stb1.st_mode & S_IFMT) != (stb2.st_mode & S_IFMT)
+       ) {
+               return 1;
+       }
+       while (1) {
+               i = fread(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));
+               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))) {
+               if (j == sz) {
+                       sz = sz * 3 / 2;
+                       p = xrealloc(p, (sz + 3) * sizeof(p[0]));
+               }
+               p[++j].value = h;
+       }
+       len[i] = j;
+       file[i] = p;
+}
+
+
+static void prune(void)
+{
+       int i, j;
+
+       for (pref = 0; pref < len[0] && pref < len[1] &&
+                file[0][pref + 1].value == file[1][pref + 1].value; pref++)
+               continue;
+       for (suff = 0; suff < len[0] - pref && suff < len[1] - pref &&
+                file[0][len[0] - suff].value == file[1][len[1] - suff].value;
+                suff++)
+               continue;
+       for (j = 0; j < 2; j++) {
+               sfile[j] = file[j] + pref;
+               slen[j] = len[j] - pref - suff;
+               for (i = 0; i <= slen[j]; i++)
+                       sfile[j][i].serial = i;
+       }
+}
+
+
+static void equiv(struct line *a, int n, struct line *b, int m, int *c)
+{
+       int i, j;
+
+       i = j = 1;
+       while (i <= n && j <= m) {
+               if (a[i].value < b[j].value)
+                       a[i++].value = 0;
+               else if (a[i].value == b[j].value)
+                       a[i++].value = j;
+               else
+                       j++;
+       }
+       while (i <= n)
+               a[i++].value = 0;
+       b[m + 1].value = 0;
+       j = 0;
+       while (++j <= m) {
+               c[j] = -b[j].serial;
+               while (b[j + 1].value == b[j].value) {
+                       j++;
+                       c[j] = b[j].serial;
+               }
+       }
+       c[j] = -1;
+}
+
+
+static int isqrt(int n)
+{
+       int y, x;
+
+       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 <= len[0]; i++)
+               J[i] = i <= pref ? i : i > len[0] - suff ? i + len[1] - len[0] : 0;
+       for (q = clist + p; q->y != 0; q = clist + q->pred)
+               J[q->x + pref] = q->y + pref;
+}
+
+
+static void unsort(struct line *f, int l, int *b)
+{
+       int *a, i;
+
+       a = xmalloc((l + 1) * sizeof(int));
+       for (i = 1; i <= l; i++)
+               a[f[i].serial] = f[i].value;
+       for (i = 1; i <= l; i++)
+               b[i] = a[i];
+       free(a);
+}
+
+
+static int skipline(FILE * f)
+{
+       int i, c;
+
+       for (i = 1; (c = getc(f)) != '\n' && c != EOF; i++)
+               continue;
+       return i;
+}
+
+
+/*
+ * Check does double duty:
+ *  1.  ferret out any fortuitous correspondences due
+ *      to confounding by hashing (which result in "jackpot")
+ *  2.  collect random access indexes to the two files
+ */
+static void check(FILE * f1, FILE * f2)
+{
+       int i, j, jackpot, c, d;
+       long ctold, ctnew;
+
+       rewind(f1);
+       rewind(f2);
+       j = 1;
+       ixold[0] = ixnew[0] = 0;
+       jackpot = 0;
+       ctold = ctnew = 0;
+       for (i = 1; i <= len[0]; i++) {
+               if (J[i] == 0) {
+                       ixold[i] = ctold += skipline(f1);
+                       continue;
+               }
+               while (j < J[i]) {
+                       ixnew[j] = ctnew += skipline(f2);
+                       j++;
+               }
+               if ((option_mask32 & FLAG_b) || (option_mask32 & FLAG_w)
+                       || (option_mask32 & 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) || (option_mask32 & 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++;
+                                       } while (isspace(c = getc(f1)));
+                                       do {
+                                               if (d == '\n')
+                                                       break;
+                                               ctnew++;
+                                       } while (isspace(d = getc(f2)));
+                               } 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);
+                                       if (d != '\n' && c != EOF)
+                                               ctnew += skipline(f2);
+                                       break;
+                               }
+                               if (c == '\n' || c == EOF)
+                                       break;
+                       }
+               }
+               ixold[i] = ctold;
+               ixnew[j] = ctnew;
+               j++;
+       }
+       for (; j <= len[1]; j++)
+               ixnew[j] = ctnew += skipline(f2);
+}
+
+
+/* shellsort CACM #201 */
+static void sort(struct line *a, int n)
+{
+       struct line *ai, *aim, w;
+       int j, m = 0, k;
+
+       if (n == 0)
+               return;
+       for (j = 1; j <= n; j *= 2)
+               m = 2 * j - 1;
+       for (m /= 2; m != 0; m /= 2) {
+               k = n - m;
+               for (j = 1; j <= k; j++) {
+                       for (ai = &a[j]; ai > a; ai -= m) {
+                               aim = &ai[m];
+                               if (aim < ai)
+                                       break;  /* wraparound */
+                               if (aim->value > ai[0].value ||
+                                       (aim->value == ai[0].value && aim->serial > ai[0].serial))
+                                       break;
+                               w.value = ai[0].value;
+                               ai[0].value = aim->value;
+                               aim->value = w.value;
+                               w.serial = ai[0].serial;
+                               ai[0].serial = aim->serial;
+                               aim->serial = w.serial;
+                       }
+               }
+       }
+}
+
+
+static void uni_range(int a, int b)
+{
+       if (a < b)
+               printf("%d,%d", a, b - a + 1);
+       else if (a == b)
+               printf("%d", b);
+       else
+               printf("%d,0", b);
+}
+
+
+static 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++;
+                       }
+               }
+       }
+}
+
+
+static int asciifile(FILE * f)
+{
+#if ENABLE_FEATURE_DIFF_BINARY
+       int i, cnt;
+#endif
+
+       if ((option_mask32 & FLAG_a) || f == NULL)
+               return 1;
+
+#if ENABLE_FEATURE_DIFF_BINARY
+       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;
+               }
+       }
+#endif
+       return 1;
+}
+
+
+/* dump accumulated "unified" diff changes */
+static void dump_unified_vec(FILE * f1, FILE * f2)
+{
+       struct context_vec *cvp = context_vec_start;
+       int lowa, upb, lowc, upd;
+       int a, b, c, d;
+       char ch;
+
+       if (context_vec_start > context_vec_ptr)
+               return;
+
+       b = d = 0;                      /* gcc */
+       lowa = MAX(1, cvp->a - context);
+       upb = MIN(len[0], context_vec_ptr->b + context);
+       lowc = MAX(1, cvp->c - context);
+       upd = MIN(len[1], context_vec_ptr->d + context);
+
+       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(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(char *file1, FILE * f1, 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 * context) + 1 &&
+                          c > context_vec_ptr->d + (2 * context) + 1) {
+               /*
+                * If this change is more than 'context' lines from the
+                * previous change, dump the record and reset it.
+                */
+               dump_unified_vec(f1, f2);
+       }
+       context_vec_ptr++;
+       context_vec_ptr->a = a;
+       context_vec_ptr->b = b;
+       context_vec_ptr->c = c;
+       context_vec_ptr->d = d;
+       anychange = 1;
+}
+
+
+static void output(char *file1, FILE * f1, char *file2, FILE * f2)
+{
+       /* Note that j0 and j1 can't be used as they are defined in math.h.
+        * This also allows the rather amusing variable 'j00'... */
+       int m, i0, i1, j00, j01;
+
+       rewind(f1);
+       rewind(f2);
+       m = len[0];
+       J[0] = 0;
+       J[m + 1] = len[1] + 1;
+       for (i0 = 1; i0 <= m; i0 = i1 + 1) {
+               while (i0 <= m && J[i0] == J[i0 - 1] + 1)
+                       i0++;
+               j00 = J[i0 - 1] + 1;
+               i1 = i0 - 1;
+               while (i1 < m && J[i1 + 1] == 0)
+                       i1++;
+               j01 = J[i1 + 1] - 1;
+               J[i1] = j01;
+               change(file1, f1, file2, f2, i0, i1, j00, j01);
+       }
+       if (m == 0) {
+               change(file1, f1, file2, f2, 1, 0, 1, len[1]);
+       }
+       if (anychange != 0 && !(option_mask32 & FLAG_q)) {
+               dump_unified_vec(f1, f2);
+       }
+}
+
+/*
+ * The following code uses an algorithm due to Harold Stone,
+ * which finds a pair of longest identical subsequences in
+ * the two files.
+ *
+ * The major goal is to generate the match vector J.
+ * J[i] is the index of the line in file1 corresponding
+ * to line i file0. J[i] = 0 if there is no
+ * such line in file1.
+ *
+ * Lines are hashed so as to work in core. All potential
+ * matches are located by sorting the lines of each file
+ * on the hash (called ``value''). In particular, this
+ * collects the equivalence classes in file1 together.
+ * Subroutine equiv replaces the value of each line in
+ * file0 by the index of the first element of its
+ * matching equivalence in (the reordered) file1.
+ * To save space equiv squeezes file1 into a single
+ * array member in which the equivalence classes
+ * are simply concatenated, except that their first
+ * members are flagged by changing sign.
+ *
+ * Next the indices that point into member are unsorted into
+ * array class according to the original order of file0.
+ *
+ * The cleverness lies in routine stone. This marches
+ * through the lines of file0, developing a vector klist
+ * of "k-candidates". At step i a k-candidate is a matched
+ * pair of lines x,y (x in file0 y in file1) such that
+ * there is a common subsequence of length k
+ * between the first i lines of file0 and the first y
+ * lines of file1, but there is no such subsequence for
+ * any smaller y. x is the earliest possible mate to y
+ * that occurs in such a subsequence.
+ *
+ * Whenever any of the members of the equivalence class of
+ * lines in file1 matable to a line in file0 has serial number
+ * less than the y of some k-candidate, that k-candidate
+ * with the smallest such y is replaced. The new
+ * k-candidate is chained (via pred) to the current
+ * k-1 candidate so that the actual subsequence can
+ * be recovered. When a member has serial number greater
+ * that the y of all k-candidates, the klist is extended.
+ * At the end, the longest subsequence is pulled out
+ * and placed in the array J by unravel
+ *
+ * With J in hand, the matches there recorded are
+ * checked against reality to assure that no spurious
+ * matches have crept in due to hashing. If they have,
+ * they are broken, and "jackpot" is recorded--a harmless
+ * matter except that a true match for a spuriously
+ * mated line may now be unnecessarily reported as a change.
+ *
+ * Much of the complexity of the program comes simply
+ * from trying to minimize core utilization and
+ * maximize the range of doable problems by dynamically
+ * allocating what is needed and reusing what is not.
+ * The core requirements for problems larger than somewhat
+ * are (in words) 2*length(file0) + length(file1) +
+ * 3*(number of k-candidates installed),  typically about
+ * 6n words for files of length n.
+ */
+static unsigned diffreg(char *ofile1, char *ofile2, int flags)
+{
+       char *file1 = ofile1;
+       char *file2 = ofile2;
+       FILE *f1 = stdin, *f2 = stdin;
+       unsigned rval;
+       int i;
+
+       anychange = 0;
+       context_vec_ptr = context_vec_start - 1;
+
+       if (S_ISDIR(stb1.st_mode) != S_ISDIR(stb2.st_mode))
+               return (S_ISDIR(stb1.st_mode) ? D_MISMATCH1 : D_MISMATCH2);
+
+       rval = D_SAME;
+
+       if (LONE_DASH(file1) && LONE_DASH(file2))
+               goto closem;
+
+       if (flags & D_EMPTY1)
+               f1 = xfopen(bb_dev_null, "r");
+       else if (NOT_LONE_DASH(file1))
+               f1 = xfopen(file1, "r");
+       if (flags & D_EMPTY2)
+               f2 = xfopen(bb_dev_null, "r");
+       else if (NOT_LONE_DASH(file2))
+               f2 = xfopen(file2, "r");
+
+/* We can't diff non-seekable stream - we use rewind(), fseek().
+ * This can be fixed (volunteers?).
+ * Meanwhile we should check it here by stat'ing input fds,
+ * but I am lazy and check that in main() instead.
+ * Check in main won't catch "diffing fifos buried in subdirectories"
+ * failure scenario - not very likely in real life... */
+
+       i = files_differ(f1, f2, flags);
+       if (i == 0)
+               goto closem;
+       else if (i != 1) {      /* 1 == ok */
+               /* error */
+               status |= 2;
+               goto closem;
+       }
+
+       if (!asciifile(f1) || !asciifile(f2)) {
+               rval = D_BINARY;
+               status |= 1;
+               goto closem;
+       }
+
+       prepare(0, f1 /*, stb1.st_size*/);
+       prepare(1, f2 /*, stb2.st_size*/);
+       prune();
+       sort(sfile[0], slen[0]);
+       sort(sfile[1], slen[1]);
+
+       member = (int *) file[1];
+       equiv(sfile[0], slen[0], sfile[1], slen[1], member);
+       member = xrealloc(member, (slen[1] + 2) * sizeof(int));
+
+       class = (int *) file[0];
+       unsort(sfile[0], slen[0], class);
+       class = xrealloc(class, (slen[0] + 2) * sizeof(int));
+
+       klist = xmalloc((slen[0] + 2) * sizeof(int));
+       clen = 0;
+       clistlen = 100;
+       clist = xmalloc(clistlen * sizeof(struct cand));
+       i = stone(class, slen[0], member, klist);
+       free(member);
+       free(class);
+
+       J = xrealloc(J, (len[0] + 2) * sizeof(int));
+       unravel(klist[i]);
+       free(clist);
+       free(klist);
+
+       ixold = xrealloc(ixold, (len[0] + 2) * sizeof(long));
+       ixnew = xrealloc(ixnew, (len[1] + 2) * sizeof(long));
+       check(f1, f2);
+       output(file1, f1, file2, f2);
+
+ closem:
+       if (anychange) {
+               status |= 1;
+               if (rval == D_SAME)
+                       rval = D_DIFFER;
+       }
+       fclose_if_not_stdin(f1);
+       fclose_if_not_stdin(f2);
+       if (file1 != ofile1)
+               free(file1);
+       if (file2 != ofile2)
+               free(file2);
+       return rval;
+}
+
+
+#if ENABLE_FEATURE_DIFF_DIR
+static void do_diff(char *dir1, char *path1, char *dir2, char *path2)
+{
+       int flags = D_HEADER;
+       int val;
+       char *fullpath1 = 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
+               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 add_to_dirlist(const char *filename,
+               struct stat ATTRIBUTE_UNUSED * sb, void *userdata,
+               int depth ATTRIBUTE_UNUSED)
+{
+       /* +2: with space for eventual trailing NULL */
+       dl = xrealloc(dl, (dl_count+2) * sizeof(dl[0]));
+       dl[dl_count] = xstrdup(filename + (int)(ptrdiff_t)userdata);
+       dl_count++;
+       return TRUE;
+}
+
+
+/* This returns a sorted directory listing. */
+static char **get_dir(char *path)
+{
+       dl_count = 0;
+       dl = xzalloc(sizeof(dl[0]));
+
+       /* If -r has been set, then the recursive_action function will be
+        * used. Unfortunately, this outputs the root directory along with
+        * the recursed paths, so use void *userdata to specify the string
+        * length of the root directory - '(void*)(strlen(path)+)'.
+        * add_to_dirlist then removes root dir prefix. */
+
+       if (option_mask32 & FLAG_r) {
+               recursive_action(path, ACTION_RECURSE|ACTION_FOLLOWLINKS,
+                                       add_to_dirlist, NULL,
+                                       (void*)(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_dir(p1);
+       dirlist2 = get_dir(p2);
+
+       /* If -S was set, find the starting point. */
+       if (start) {
+               while (*dirlist1 != NULL && strcmp(*dirlist1, start) < 0)
+                       dirlist1++;
+               while (*dirlist2 != NULL && strcmp(*dirlist2, start) < 0)
+                       dirlist2++;
+               if ((*dirlist1 == NULL) || (*dirlist2 == NULL))
+                       bb_error_msg(bb_msg_invalid_arg, "NULL", "-S");
+       }
+
+       /* Now that both dirlist1 and dirlist2 contain sorted directory
+        * listings, we can start to go through dirlist1. If both listings
+        * contain the same file, then do a normal diff. Otherwise, behaviour
+        * is determined by whether the -N flag is set. */
+       while (*dirlist1 != NULL || *dirlist2 != NULL) {
+               dp1 = *dirlist1;
+               dp2 = *dirlist2;
+               pos = dp1 == NULL ? 1 : dp2 == NULL ? -1 : strcmp(dp1, dp2);
+               if (pos == 0) {
+                       do_diff(p1, dp1, p2, dp2);
+                       dirlist1++;
+                       dirlist2++;
+               } else if (pos < 0) {
+                       if (option_mask32 & FLAG_N)
+                               do_diff(p1, dp1, p2, NULL);
+                       else
+                               print_only(p1, strlen(p1) + 1, dp1);
+                       dirlist1++;
+               } else {
+                       if (option_mask32 & FLAG_N)
+                               do_diff(p1, NULL, p2, dp2);
+                       else
+                               print_only(p2, strlen(p2) + 1, dp2);
+                       dirlist2++;
+               }
+       }
+}
+#endif
+
+
+int diff_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int diff_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       bool 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, &start, &context);
+       /*argc -= optind;*/
+       argv += optind;
+       while (L_arg) {
+               if (label1 && label2)
+                       bb_show_usage();
+               if (!label1)
+                       label1 = L_arg->data;
+               else { /* then label2 is NULL */
+                       label2 = label1;
+                       label1 = L_arg->data;
+               }
+               /* we leak L_arg here... */
+               L_arg = L_arg->link;
+       }
+
+       /*
+        * 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 = 1;
+       } else
+               xstat(f1, &stb1);
+       if (LONE_DASH(f2)) {
+               fstat(STDIN_FILENO, &stb2);
+               gotstdin = 1;
+       } else
+               xstat(f2, &stb2);
+       if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode)))
+               bb_error_msg_and_die("can't compare - to a directory");
+       if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
+#if ENABLE_FEATURE_DIFF_DIR
+               diffdir(f1, f2);
+#else
+               bb_error_msg_and_die("directory comparison not supported");
+#endif
+       } else {
+               if (S_ISDIR(stb1.st_mode)) {
+                       f1 = concat_path_file(f1, f2);
+                       xstat(f1, &stb1);
+               }
+               if (S_ISDIR(stb2.st_mode)) {
+                       f2 = concat_path_file(f2, f1);
+                       xstat(f2, &stb2);
+               }
+/* XXX: FIXME: */
+/* We can't diff e.g. stdin supplied by a pipe - we use rewind(), fseek().
+ * This can be fixed (volunteers?) */
+               if (!S_ISREG(stb1.st_mode) || !S_ISREG(stb2.st_mode))
+                       bb_error_msg_and_die("can't diff non-seekable stream");
+               print_status(diffreg(f1, f2, 0), f1, f2, NULL);
+       }
+       return status;
+}
diff --git a/editors/ed.c b/editors/ed.c
new file mode 100644 (file)
index 0000000..9606cfd
--- /dev/null
@@ -0,0 +1,1054 @@
+/* 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 ATTRIBUTE_UNUSED, 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, *newname, 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;
+                               }
+                               newname = strdup(cp);
+                               if (newname == NULL) {
+                                       bb_error_msg("no memory for file name");
+                                       break;
+                               }
+                               free(fileName);
+                               fileName = newname;
+                               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 = strdup(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(1, lp->data, lp->len);
+                       setCurNum(num1++);
+                       lp = lp->next;
+                       continue;
+               }
+
+               /*
+                * Show control characters and characters with the
+                * high bit set specially.
+                */
+               cp = lp->data;
+               count = lp->len;
+
+               if ((count > 0) && (cp[count - 1] == '\n'))
+                       count--;
+
+               while (count-- > 0) {
+                       ch = (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..9a678e1
--- /dev/null
@@ -0,0 +1,271 @@
+/* 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 <getopt.h>
+
+#include "libbb.h"
+
+static unsigned int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count)
+{
+       unsigned int i = 0;
+
+       while (src_stream && (i < lines_count)) {
+               char *line;
+               line = xmalloc_fgets(src_stream);
+               if (line == NULL) {
+                       break;
+               }
+               if (fputs(line, dest_stream) == EOF) {
+                       bb_perror_msg_and_die("error writing to new file");
+               }
+               free(line);
+
+               i++;
+       }
+       return i;
+}
+
+/* If patch_level is -1 it will remove all directory names
+ * char *line must be greater than 4 chars
+ * returns NULL if the file doesnt exist or error
+ * returns malloc'ed filename
+ */
+
+static char *extract_filename(char *line, int patch_level)
+{
+       char *temp, *filename_start_ptr = line + 4;
+       int i;
+
+       /* Terminate string at end of source filename */
+       temp = strchrnul(filename_start_ptr, '\t');
+       *temp = '\0';
+
+       /* Skip over (patch_level) number of leading directories */
+       if (patch_level == -1)
+               patch_level = INT_MAX;
+       for (i = 0; i < patch_level; i++) {
+               temp = strchr(filename_start_ptr, '/');
+               if (!temp)
+                       break;
+               filename_start_ptr = temp + 1;
+       }
+
+       return xstrdup(filename_start_ptr);
+}
+
+int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int patch_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int patch_level = -1;
+       char *patch_line;
+       int ret;
+       FILE *patch_file = NULL;
+       struct stat saved_stat;
+       
+       {
+               char *p, *i;
+               ret = getopt32(argv, "p:i:", &p, &i);
+               if (ret & 1)
+                       patch_level = xatol_range(p, -1, USHRT_MAX);
+               if (ret & 2) {
+                       patch_file = xfopen(i, "r");
+               } else {
+                       patch_file = stdin;
+               }
+               ret = 0;
+       }
+
+       patch_line = xmalloc_getline(patch_file);
+       while (patch_line) {
+               FILE *src_stream;
+               FILE *dst_stream;
+               char *original_filename;
+               char *new_filename;
+               char *backup_filename;
+               unsigned int src_cur_line = 1;
+               unsigned int dest_cur_line = 0;
+               unsigned int dest_beg_line;
+               unsigned int bad_hunk_count = 0;
+               unsigned int hunk_count = 0;
+               char copy_trailing_lines_flag = 0;
+
+               /* Skip everything upto the "---" marker
+                * No need to parse the lines "Only in <dir>", and "diff <args>"
+                */
+               while (patch_line && strncmp(patch_line, "--- ", 4) != 0) {
+                       free(patch_line);
+                       patch_line = xmalloc_getline(patch_file);
+               }
+               /* FIXME: patch_line NULL check?? */
+
+               /* Extract the filename used before the patch was generated */
+               original_filename = extract_filename(patch_line, patch_level);
+               free(patch_line);
+
+               patch_line = xmalloc_getline(patch_file);
+               /* FIXME: NULL check?? */
+               if (strncmp(patch_line, "+++ ", 4) != 0) {
+                       ret = 2;
+                       bb_error_msg("invalid patch");
+                       continue;
+               }
+               new_filename = extract_filename(patch_line, patch_level);
+               free(patch_line);
+               
+               /* Get access rights from the file to be patched, -1 file does not exist */
+               if (stat(new_filename, &saved_stat)) {
+                       char *line_ptr;
+                       /* Create leading directories */
+                       line_ptr = strrchr(new_filename, '/');
+                       if (line_ptr) {
+                               *line_ptr = '\0';
+                               bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
+                               *line_ptr = '/';
+                       }
+                       dst_stream = xfopen(new_filename, "w+");
+                       backup_filename = NULL;
+               } else {
+                       backup_filename = xmalloc(strlen(new_filename) + 6);
+                       strcpy(backup_filename, new_filename);
+                       strcat(backup_filename, ".orig");
+                       xrename(new_filename, backup_filename);
+                       dst_stream = xfopen(new_filename, "w");
+                       fchmod(fileno(dst_stream), saved_stat.st_mode);
+               }
+
+               if ((backup_filename == NULL) || stat(original_filename, &saved_stat)) {
+                       src_stream = NULL;
+               } else {
+                       if (strcmp(original_filename, new_filename) == 0) {
+                               src_stream = xfopen(backup_filename, "r");
+                       } else {
+                               src_stream = xfopen(original_filename, "r");
+                       }
+               }
+
+               printf("patching file %s\n", new_filename);
+
+               /* Handle each hunk */
+               patch_line = xmalloc_fgets(patch_file);
+               while (patch_line) {
+                       unsigned int count;
+                       unsigned int src_beg_line;
+                       unsigned int unused;
+                       unsigned int hunk_offset_start = 0;
+                       int hunk_error = 0;
+
+                       /* This bit should be improved */
+                       if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) &&
+                               (sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) &&
+                               (sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) {
+                               /* No more hunks for this file */
+                               break;
+                       }
+                       free(patch_line);
+                       hunk_count++;
+
+                       if (src_beg_line && dest_beg_line) {
+                               /* Copy unmodified lines upto start of hunk */
+                               /* src_beg_line will be 0 if its a new file */
+                               count = src_beg_line - src_cur_line;
+                               if (copy_lines(src_stream, dst_stream, count) != count) {
+                                       bb_error_msg_and_die("bad src file");
+                               }
+                               src_cur_line += count;
+                               dest_cur_line += count;
+                               copy_trailing_lines_flag = 1;
+                       }
+                       hunk_offset_start = src_cur_line;
+
+                       while ((patch_line = xmalloc_fgets(patch_file)) != NULL) {
+                               if ((*patch_line == '-') || (*patch_line == ' ')) {
+                                       char *src_line = NULL;
+                                       if (src_stream) {
+                                               src_line = xmalloc_fgets(src_stream);
+                                               if (!src_line) {
+                                                       hunk_error++;
+                                                       break;
+                                               } else {
+                                                       src_cur_line++;
+                                               }
+                                               if (strcmp(src_line, patch_line + 1) != 0) {
+                                                       bb_error_msg("hunk #%d FAILED at %d", hunk_count, hunk_offset_start);
+                                                       hunk_error++;
+                                                       free(patch_line);
+                                                       /* Probably need to find next hunk, etc... */
+                                                       /* but for now we just bail out */
+                                                       patch_line = NULL;
+                                                       break;
+                                               }
+                                               free(src_line);
+                                       }
+                                       if (*patch_line == ' ') {
+                                               fputs(patch_line + 1, dst_stream);
+                                               dest_cur_line++;
+                                       }
+                               } else if (*patch_line == '+') {
+                                       fputs(patch_line + 1, dst_stream);
+                                       dest_cur_line++;
+                               } else {
+                                       break;
+                               }
+                               free(patch_line);
+                       }
+                       if (hunk_error) {
+                               bad_hunk_count++;
+                       }
+               }
+
+               /* Cleanup last patched file */
+               if (copy_trailing_lines_flag) {
+                       copy_lines(src_stream, dst_stream, -1);
+               }
+               if (src_stream) {
+                       fclose(src_stream);
+               }
+               if (dst_stream) {
+                       fclose(dst_stream);
+               }
+               if (bad_hunk_count) {
+                       if (!ret) {
+                               ret = 1;
+                       }
+                       bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count);
+               } else {
+                       /* It worked, we can remove the backup */
+                       if (backup_filename) {
+                               unlink(backup_filename);
+                       }
+                       if ((dest_cur_line == 0) || (dest_beg_line == 0)) {
+                               /* The new patched file is empty, remove it */
+                               xunlink(new_filename);
+                               if (strcmp(new_filename, original_filename) != 0)
+                                       xunlink(original_filename);
+                       }
+               }
+       }
+
+       /* 0 = SUCCESS
+        * 1 = Some hunks failed
+        * 2 = More serious problems
+        */
+       return ret;
+}
diff --git a/editors/sed.c b/editors/sed.c
new file mode 100644 (file)
index 0000000..32911f8
--- /dev/null
@@ -0,0 +1,1352 @@
+/* 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(sed_cmd->string, "w");
+                       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;
+       int 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(G.input_file_list,
+                       (G.input_file_count + 1) * sizeof(FILE *));
+       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(sed_cmd->string, "r");
+                               if (rfile) {
+                                       char *line;
+
+                                       while ((line = xmalloc_getline(rfile))
+                                                       != NULL)
+                                               append(line);
+                                       xprint_and_close_file(rfile);
+                               }
+
+                               break;
+                       }
+
+                       /* Write pattern space to file. */
+                       case 'w':
+                               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) {
+                               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 ATTRIBUTE_UNUSED, 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(opt_e->data);
+               opt_e = opt_e->link;
+               /* we leak opt_e here... */
+       }
+       while (opt_f) { // -f
+               char *line;
+               FILE *cmdfile;
+               cmdfile = xfopen(opt_f->data, "r");
+               while ((line = xmalloc_getline(cmdfile)) != NULL) {
+                       add_cmd(line);
+                       free(line);
+               }
+               fclose(cmdfile);
+               opt_f = opt_f->link;
+               /* we leak opt_f here... */
+       }
+       /* 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..22bd2ff
--- /dev/null
@@ -0,0 +1,4044 @@
+/* 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,
+};
+
+// Misc. non-Ascii keys that report an escape sequence
+#define VI_K_UP                        (char)128       // cursor key Up
+#define VI_K_DOWN              (char)129       // cursor key Down
+#define VI_K_RIGHT             (char)130       // Cursor Key Right
+#define VI_K_LEFT              (char)131       // cursor key Left
+#define VI_K_HOME              (char)132       // Cursor Key Home
+#define VI_K_END               (char)133       // Cursor Key End
+#define VI_K_INSERT            (char)134       // Cursor Key Insert
+#define VI_K_DELETE            (char)135       // Cursor Key Insert
+#define VI_K_PAGEUP            (char)136       // Cursor Key Page Up
+#define VI_K_PAGEDOWN          (char)137       // Cursor Key Page Down
+#define VI_K_FUN1              (char)138       // Function Key F1
+#define VI_K_FUN2              (char)139       // Function Key F2
+#define VI_K_FUN3              (char)140       // Function Key F3
+#define VI_K_FUN4              (char)141       // Function Key F4
+#define VI_K_FUN5              (char)142       // Function Key F5
+#define VI_K_FUN6              (char)143       // Function Key F6
+#define VI_K_FUN7              (char)144       // Function Key F7
+#define VI_K_FUN8              (char)145       // Function Key F8
+#define VI_K_FUN9              (char)146       // Function Key F9
+#define VI_K_FUN10             (char)147       // Function Key F10
+#define VI_K_FUN11             (char)148       // Function Key F11
+#define VI_K_FUN12             (char)149       // Function Key F12
+
+/* 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";
+
+
+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 */
+
+static 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)
+
+
+static smallint editing;        // >0 while we are editing a file
+                                // [code audit says "can be 0 or 1 only"]
+static smallint cmd_mode;       // 0=command  1=insert 2=replace
+static smallint file_modified;  // buffer contents changed
+static smallint last_file_modified = -1;
+static int fn_start;            // index of first cmd line file name
+static int save_argc;           // how many file names on cmd line
+static int cmdcnt;              // repetition count
+static int rows, columns;       // the terminal screen is this size
+static int crow, ccol;          // cursor is on Crow x Ccol
+static int offset;              // chars scrolled off the screen to the left
+static char *status_buffer;     // mesages to the user
+#define STATUS_BUFFER_LEN  200
+static int have_status_msg;     // is default edit status needed?
+                                // [don't make smallint!]
+static int last_status_cksum;   // hash of current status line
+static char *current_filename;               // current file name
+//static char *text, *end;        // pointers to the user data in memory
+static char *screen;            // pointer to the virtual screen buffer
+static int screensize;          //            and its size
+static char *screenbegin;       // index into text[], of top line on the screen
+//static char *dot;               // where all the action takes place
+static int tabstop;
+static char erase_char;         // the users erase character
+static char last_input_char;    // last char read from user
+static char last_forward_char;  // last char searched for with 'f'
+
+#if ENABLE_FEATURE_VI_READONLY
+//static smallint vi_readonly, readonly;
+static smallint readonly_mode = 0;
+#define SET_READONLY_FILE(flags)        ((flags) |= 0x01)
+#define SET_READONLY_MODE(flags)        ((flags) |= 0x02)
+#define UNSET_READONLY_FILE(flags)      ((flags) &= 0xfe)
+#else
+#define readonly_mode 0
+#define SET_READONLY_FILE(flags)
+#define SET_READONLY_MODE(flags)
+#define UNSET_READONLY_FILE(flags)
+#endif
+
+#if ENABLE_FEATURE_VI_DOT_CMD
+static smallint adding2q;              // are we currently adding user input to q
+static char *last_modifying_cmd;       // [MAX_INPUT_LEN] last modifying cmd for "."
+static smallint lmc_len;               // length of last_modifying_cmd
+static char *ioq, *ioq_start;           // pointer to string for get_one_char to "read"
+#endif
+#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
+static int last_row;           // where the cursor was last moved to
+#endif
+#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
+static int my_pid;
+#endif
+#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
+static char *modifying_cmds;            // cmds that modify text[]
+#endif
+#if ENABLE_FEATURE_VI_SEARCH
+static char *last_search_pattern;      // last pattern from a '/' or '?' search
+#endif
+
+/* Moving biggest data to malloced space... */
+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
+#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
+       /* a few references only */
+#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 CRASME mode uses it as generated command buffer too
+#if ENABLE_FEATURE_VI_CRASHME
+       char readbuffer[128];
+#else
+       char readbuffer[32];
+#endif
+
+       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 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 INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} 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(char);      // 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'
+static char *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
+static char *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 char readit(void);      // read (maybe cursor) key from stdin
+static char 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
+static int file_insert(const char *, char *, int);
+#else
+static int file_insert(const char *, char *);
+#endif
+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 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
+static char *string_insert(char *, 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;
+       RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
+
+#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
+       my_pid = getpid();
+#endif
+
+       INIT_G();
+
+#if ENABLE_FEATURE_VI_CRASHME
+       srand((long) my_pid);
+#endif
+
+       status_buffer = STATUS_BUFFER;
+       last_status_cksum = 0;
+       text = NULL;
+
+#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;
+#if ENABLE_FEATURE_VI_YANKMARK
+       memset(reg, 0, sizeof(reg)); // init the yank regs
+#endif
+#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
+       modifying_cmds = (char *) "aAcCdDiIJoOpPrRsxX<>~";      // cmds modifying text[]
+#endif
+
+       //  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 * 2;
+       if (text_size < 10240)
+               text_size = 10240;      // have a minimum size for new files
+       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
+                       USE_FEATURE_VI_READONLY(, 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)
+{
+       char c;
+       int size;
+
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+       int sig;
+#endif
+#if ENABLE_FEATURE_VI_YANKMARK
+       static char *cur_line;
+#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;
+                               dot = string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n"); // insert the string
+                               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
+                && 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 (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
+       }
+       //-------------------------------------------------------------------
+
+       place_cursor(rows, 0, FALSE);   // go to bottom of screen
+       clear_to_eol();         // Erase to end of line
+       cookmode();
+}
+
+//----- 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);   // count lines
+                       }
+               }
+       }
+#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>
+               place_cursor(rows - 1, 0, FALSE);       // go to Status line
+               clear_to_eol();                 // clear the line
+               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
+               place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               cookmode();
+               show_help();
+               rawmode();
+               Hit_Return();
+       } else if (strncasecmp(cmd, "list", i) == 0) {  // literal print line
+               if (b < 0) {    // no addr given- use defaults
+                       q = begin_line(dot);    // assume .,. for the range
+                       r = end_line(dot);
+               }
+               place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               puts("\r");
+               for (; q <= r; q++) {
+                       int c_is_no_print;
+
+                       c = *q;
+                       c_is_no_print = (c & 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);
+               ch = file_insert(fn, q  USE_FEATURE_VI_READONLY(, 0));
+               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++;
+               }
+       } 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
+                       place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
+                       clear_to_eol(); // clear the line
+                       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) {
+                               // we found the "find" pattern - delete it
+                               text_hole_delete(buf1, buf1 + strlen(F) - 1);
+                               // inset the "replace" patern
+                               string_insert(buf1, R); // insert the string
+                               // 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("%s", 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)
+{
+       char 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;
+                       } else {
+                               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 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[-1] == '\n' && p > text)
+               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 == '\n' && p < end - 1)
+               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
+               p--;                    // backup onto ^
+               refresh(FALSE); // show the ^
+               c = get_one_char();
+               *p = c;
+               p++;
+               file_modified++;        // has the file been modified
+       } else if (c == 27) {   // Is this an ESC?
+               cmd_mode = 0;
+               cmdcnt = 0;
+               end_cmd_q();    // stop adding to q
+               last_status_cksum = 0;  // force status update
+               if ((p[-1] != '\n') && (dot > text)) {
+                       p--;
+               }
+       } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
+               //     123456789
+               if ((p[-1] != '\n') && (dot>text)) {
+                       p--;
+                       p = text_hole_delete(p, p);     // shrink buffer 1 char
+               }
+       } else {
+               // insert a char into text[]
+               char *sp;               // "save p"
+
+               if (c == 13)
+                       c = '\n';       // translate \r to \n
+               sp = p;                 // remember addr of insert
+               p = stupid_insert(p, c);        // insert the char
+#if ENABLE_FEATURE_VI_SETOPTS
+               if (showmatch && strchr(")]}", *sp) != NULL) {
+                       showmatching(sp);
+               }
+               if (autoindent && c == '\n') {  // auto indent the new line
+                       char *q;
+
+                       q = prev_line(p);       // use prev line as templet
+                       for (; isblank(*q); q++) {
+                               p = stupid_insert(p, *q);       // insert the char
+                       }
+               }
+#endif
+       }
+       return p;
+}
+
+static char *stupid_insert(char * p, char c) // stupidly insert the char c at 'p'
+{
+       p = text_hole_make(p, 1);
+       if (p != 0) {
+               *p = c;
+               file_modified++;        // has the file been modified
+               p++;
+       }
+       return p;
+}
+
+static 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[]
+static char *text_hole_make(char * p, int size)        // at "p", make a 'size' byte hole
+{
+       char *src, *dest;
+       int cnt;
+
+       if (size <= 0)
+               goto thm0;
+       src = p;
+       dest = p + size;
+       cnt = end - src;        // the rest of buffer
+       if ( ((end + size) >= (text + text_size)) // TODO: realloc here
+                       || memmove(dest, src, cnt) != dest) {
+               status_line_bold("can't create room for new characters");
+               p = NULL;
+               goto thm0;
+       }
+       memset(p, ' ', size);   // clear new hole
+       end += size;            // adjust the new END
+       file_modified++;        // has the file been modified
+ thm0:
+       return p;
+}
+
+//  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
+       if (memmove(dest, src, cnt) != dest) {
+               status_line_bold("can't delete the character");
+       }
+ thd_atend:
+       end = end - hole_size;  // adjust the new END
+       if (dest >= end)
+               dest = end - 1; // make sure dest in below end-1
+       if (end <= text)
+               dest = end = text;      // keep pointers valid
+       file_modified++;        // has the file been modified
+ thd0:
+       return dest;
+}
+
+// copy text into register, then delete text.
+// if dist <= 0, do not include, or go past, a NewLine
+//
+static 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 (!last_modifying_cmd)
+               last_modifying_cmd = xzalloc(MAX_INPUT_LEN);
+       // 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
+static char *string_insert(char * p, char * s) // insert the string at 'p'
+{
+       int cnt, i;
+
+       i = strlen(s);
+       if (text_hole_make(p, i)) {
+               strncpy(p, s, i);
+               for (cnt = 0; *s != '\0'; s++) {
+                       if (*s == '\n')
+                               cnt++;
+               }
+#if ENABLE_FEATURE_VI_YANKMARK
+               status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
+#endif
+       }
+       return p;
+}
+#endif
+
+#if ENABLE_FEATURE_VI_YANKMARK
+static char *text_yank(char * p, char * q, int dest)   // copy text into a register
+{
+       char *t;
+       int cnt;
+
+       if (q < p) {            // they are backwards- reverse them
+               t = q;
+               q = p;
+               p = t;
+       }
+       cnt = q - p + 1;
+       t = reg[dest];
+       free(t);                //  if already a yank register, free it
+       t = xmalloc(cnt + 1);   // get a new register
+       memset(t, '\0', cnt + 1);       // clear new text[]
+       strncpy(t, p, cnt);     // copy text[] into bufer
+       reg[dest] = t;
+       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(0, TCSANOW, &term_vi);
+}
+
+static void cookmode(void)
+{
+       fflush(stdout);
+       tcsetattr(0, TCSANOW, &term_orig);
+}
+
+//----- Come here when we get a window resize signal ---------
+#if ENABLE_FEATURE_VI_USE_SIGNALS
+static void winch_sig(int sig ATTRIBUTE_UNUSED)
+{
+       // 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 ATTRIBUTE_UNUSED)
+{
+       rawmode();                      // terminal to "raw"
+       last_status_cksum = 0;  // force status update
+       redraw(TRUE);           // re-draw the screen
+
+       signal(SIGTSTP, suspend_sig);
+       signal(SIGCONT, SIG_DFL);
+       kill(my_pid, SIGCONT);
+}
+
+//----- Come here when we get a Suspend signal -------------------
+static void suspend_sig(int sig ATTRIBUTE_UNUSED)
+{
+       place_cursor(rows - 1, 0, FALSE);       // go to bottom of screen
+       clear_to_eol();         // Erase to end of line
+       cookmode();                     // terminal to "cooked"
+
+       signal(SIGCONT, cont_sig);
+       signal(SIGTSTP, SIG_DFL);
+       kill(my_pid, SIGTSTP);
+}
+
+//----- Come here when we get a signal ---------------------------
+static void catch_sig(int sig)
+{
+       signal(SIGINT, catch_sig);
+       if (sig)
+               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;
+}
+
+static int chars_to_parse;
+
+//----- IO Routines --------------------------------------------
+static char readit(void)       // read (maybe cursor) key from stdin
+{
+       char c;
+       int n;
+       struct esc_cmds {
+               const char seq[4];
+               char val;
+       };
+
+       static const struct esc_cmds esccmds[] = {
+               {"OA"  , VI_K_UP      },   // cursor key Up
+               {"OB"  , VI_K_DOWN    },   // cursor key Down
+               {"OC"  , VI_K_RIGHT   },   // Cursor Key Right
+               {"OD"  , VI_K_LEFT    },   // cursor key Left
+               {"OH"  , VI_K_HOME    },   // Cursor Key Home
+               {"OF"  , VI_K_END     },   // Cursor Key End
+               {"[A"  , VI_K_UP      },   // cursor key Up
+               {"[B"  , VI_K_DOWN    },   // cursor key Down
+               {"[C"  , VI_K_RIGHT   },   // Cursor Key Right
+               {"[D"  , VI_K_LEFT    },   // cursor key Left
+               {"[H"  , VI_K_HOME    },   // Cursor Key Home
+               {"[F"  , VI_K_END     },   // Cursor Key End
+               {"[1~" , VI_K_HOME    },   // Cursor Key Home
+               {"[2~" , VI_K_INSERT  },   // Cursor Key Insert
+               {"[3~" , VI_K_DELETE  },   // Cursor Key Delete
+               {"[4~" , VI_K_END     },   // Cursor Key End
+               {"[5~" , VI_K_PAGEUP  },   // Cursor Key Page Up
+               {"[6~" , VI_K_PAGEDOWN},   // Cursor Key Page Down
+               {"OP"  , VI_K_FUN1    },   // Function Key F1
+               {"OQ"  , VI_K_FUN2    },   // Function Key F2
+               {"OR"  , VI_K_FUN3    },   // Function Key F3
+               {"OS"  , VI_K_FUN4    },   // Function Key F4
+               // careful: these have no terminating NUL!
+               {"[11~", VI_K_FUN1    },   // Function Key F1
+               {"[12~", VI_K_FUN2    },   // Function Key F2
+               {"[13~", VI_K_FUN3    },   // Function Key F3
+               {"[14~", VI_K_FUN4    },   // Function Key F4
+               {"[15~", VI_K_FUN5    },   // Function Key F5
+               {"[17~", VI_K_FUN6    },   // Function Key F6
+               {"[18~", VI_K_FUN7    },   // Function Key F7
+               {"[19~", VI_K_FUN8    },   // Function Key F8
+               {"[20~", VI_K_FUN9    },   // Function Key F9
+               {"[21~", VI_K_FUN10   },   // Function Key F10
+               {"[23~", VI_K_FUN11   },   // Function Key F11
+               {"[24~", VI_K_FUN12   },   // Function Key F12
+       };
+       enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
+
+       fflush(stdout);
+       n = chars_to_parse;
+       // get input from User- are there already input chars in Q?
+       if (n <= 0) {
+               // the Q is empty, wait for a typed char
+               n = safe_read(0, readbuffer, sizeof(readbuffer));
+               if (n < 0) {
+                       if (errno == EBADF || errno == EFAULT || errno == EINVAL
+                        || errno == EIO)
+                               editing = 0; // want to exit
+                       errno = 0;
+               }
+               if (n <= 0)
+                       return 0;       // error
+               if (readbuffer[0] == 27) {
+                       // This is an ESC char. Is this Esc sequence?
+                       // Could be bare Esc key. See if there are any
+                       // more chars to read after the ESC. This would
+                       // be a Function or Cursor Key sequence.
+                       struct pollfd pfd[1];
+                       pfd[0].fd = 0;
+                       pfd[0].events = POLLIN;
+                       // keep reading while there are input chars, and room in buffer
+                       // for a complete ESC sequence (assuming 8 chars is enough)
+                       while (safe_poll(pfd, 1, 0) > 0 && n <= (sizeof(readbuffer) - 8)) {
+                               // read the rest of the ESC string
+                               int r = safe_read(0, readbuffer + n, sizeof(readbuffer) - n);
+                               if (r > 0)
+                                       n += r;
+                       }
+               }
+               chars_to_parse = n;
+       }
+       c = readbuffer[0];
+       if (c == 27 && n > 1) {
+               // Maybe cursor or function key?
+               const struct esc_cmds *eindex;
+
+               for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
+                       int cnt = strnlen(eindex->seq, 4);
+                       if (n <= cnt)
+                               continue;
+                       if (strncmp(eindex->seq, readbuffer + 1, cnt) != 0)
+                               continue;
+                       c = eindex->val; // magic char value
+                       n = cnt + 1; // squeeze out the ESC sequence
+                       goto found;
+               }
+               // defined ESC sequence not found
+       }
+       n = 1;
+ found:
+       // remove key sequence from Q
+       chars_to_parse -= n;
+       memmove(readbuffer, readbuffer + n, sizeof(readbuffer) - n);
+       return c;
+}
+
+//----- IO Routines --------------------------------------------
+static char get_one_char(void)
+{
+       char 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
+                       c = *ioq++;
+                       if (c == '\0') {
+                               // the end of the q, read from STDIN
+                               free(ioq_start);
+                               ioq_start = ioq = 0;
+                               c = readit();   // get the users input
+                       }
+               }
+       } else {
+               // adding STDIN chars to q
+               c = readit();   // get the users input
+               if (last_modifying_cmd != NULL) {
+                       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)
+{
+       static char *buf; // [MAX_INPUT_LEN]
+
+       char c;
+       int i;
+
+       if (!buf) buf = xmalloc(MAX_INPUT_LEN);
+
+       strcpy(buf, prompt);
+       last_status_cksum = 0;  // force status update
+       place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
+       clear_to_eol();         // clear the line
+       write1(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 {
+                       buf[i] = c;
+                       buf[++i] = '\0';
+                       bb_putchar(c);
+               }
+       }
+       refresh(FALSE);
+       return 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;
+}
+
+static int file_insert(const char * fn, char *p
+               USE_FEATURE_VI_READONLY(, 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 ((statbuf.st_mode & S_IFREG) == 0) {
+               // This is not a regular file
+               status_line_bold("\"%s\" Not a regular file", fn);
+               goto fi0;
+       }
+       /* // this check is done by open()
+       if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
+               // dont have any read permissions
+               status_line_bold("\"%s\" Not readable", 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);
+       if (p == NULL)
+               goto fi0;
+       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;
+       fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666);
+       if (fd < 0)
+               return -1;
+       cnt = last - first + 1;
+       charcnt = full_write(fd, first, cnt);
+       if (charcnt == cnt) {
+               // good write
+               //file_modified = FALSE; // the file has not been modified
+       } else {
+               charcnt = 0;
+       }
+       close(fd);
+       return charcnt;
+}
+
+//----- Terminal Drawing ---------------------------------------
+// The terminal is made up of 'rows' line of 'columns' columns.
+// classically this would be 24 x 80.
+//  screen coordinates
+//  0,0     ...     0,79
+//  1,0     ...     1,79
+//  .       ...     .
+//  .       ...     .
+//  22,0    ...     22,79
+//  23,0    ...     23,79   <- status line
+
+//----- Move the cursor to row x col (count from 0, not 1) -------
+static void place_cursor(int row, int col, int optimize)
+{
+       char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
+       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) {
+               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
+               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
+}
+
+//----- 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
+               place_cursor(rows - 1, 0, FALSE);       // put cursor on status line
+               write1(status_buffer);
+               clear_to_eol();
+               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 int tot;
+       static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
+       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 */
+}
+
+//----- Force refresh of all Lines -----------------------------
+static void redraw(int full_screen)
+{
+       place_cursor(0, 0, FALSE);      // put cursor in correct place
+       clear_to_eos();         // tel terminal to erase display
+       screen_erase();         // erase the internal screen buffer
+       last_status_cksum = 0;  // force status update
+       refresh(full_screen);   // this will redraw the entire display
+       show_status_line();
+}
+
+//----- Format a text[] line into a buffer ---------------------
+static 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)
+{
+       static int old_offset;
+
+       int li, changed;
+       char *tp, *sp;          // pointer into text[] and screen[]
+
+       if (ENABLE_FEATURE_VI_WIN_RESIZE) {
+               int 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;
+}
+
+//---------------------------------------------------------------------
+//----- 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(char c)
+{
+       const char *msg = msg; // for compiler
+       char c1, *p, *q, *save_dot;
+       char buf[12];
+       int dir = dir; // for compiler
+       int cnt, i, j;
+
+//     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 VI_K_UP:
+               case VI_K_DOWN:
+               case VI_K_LEFT:
+               case VI_K_RIGHT:
+               case VI_K_HOME:
+               case VI_K_END:
+               case VI_K_PAGEUP:
+               case VI_K_PAGEDOWN:
+                       goto key_cmd_mode;
+       }
+
+       if (cmd_mode == 2) {
+               //  flip-flop Insert/Replace mode
+               if (c == VI_K_INSERT)
+                       goto dc_i;
+               // we are 'R'eplacing the current *dot with new char
+               if (*dot == '\n') {
+                       // don't Replace past E-o-l
+                       cmd_mode = 1;   // convert to insert
+               } else {
+                       if (1 <= c || Isprint(c)) {
+                               if (c != 27)
+                                       dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
+                               dot = char_insert(dot, c);      // insert new char
+                       }
+                       goto dc1;
+               }
+       }
+       if (cmd_mode == 1) {
+               //  hitting "Insert" twice means "R" replace mode
+               if (c == VI_K_INSERT) goto dc5;
+               // insert the char c at "dot"
+               if (1 <= c || Isprint(c)) {
+                       dot = char_insert(dot, c);
+               }
+               goto dc1;
+       }
+
+ key_cmd_mode:
+       switch (c) {
+               //case 0x01:    // soh
+               //case 0x09:    // ht
+               //case 0x0b:    // vt
+               //case 0x0e:    // so
+               //case 0x0f:    // si
+               //case 0x10:    // dle
+               //case 0x11:    // dc1
+               //case 0x13:    // dc3
+#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 VI_K_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 VI_K_PAGEDOWN:     // Cursor Key Page Down
+               dot_scroll(rows - 2, 1);
+               break;
+       case 7:                 // ctrl-G  show current status
+               last_status_cksum = 0;  // force status update
+               break;
+       case 'h':                       // h- move left
+       case VI_K_LEFT: // cursor key Left
+       case 8:         // ctrl-H- move left    (This may be ERASE char)
+       case 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 VI_K_DOWN: // cursor key Down
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_next();             // go to next B-o-l
+               dot = move_to_col(dot, ccol + offset);  // try stay in same col
+               break;
+       case 12:                        // ctrl-L  force redraw whole screen
+       case 18:                        // ctrl-R  force redraw
+               place_cursor(0, 0, FALSE);      // put cursor in correct place
+               clear_to_eos(); // tel terminal to erase display
+               mysleep(10);
+               screen_erase(); // erase the internal screen buffer
+               last_status_cksum = 0;  // force status update
+               refresh(TRUE);  // this will redraw the entire display
+               break;
+       case 13:                        // Carriage Return ^M
+       case '+':                       // +- goto next line
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_next();
+               dot_skip_over_ws();
+               break;
+       case 21:                        // ctrl-U  scroll up   half screen
+               dot_scroll((rows - 2) / 2, -1);
+               break;
+       case 25:                        // ctrl-Y  scroll up one line
+               dot_scroll(1, -1);
+               break;
+       case 27:                        // esc
+               if (cmd_mode == 0)
+                       indicate_error(c);
+               cmd_mode = 0;   // stop insrting
+               end_cmd_q();
+               last_status_cksum = 0;  // force status update
+               break;
+       case ' ':                       // move right
+       case 'l':                       // move right
+       case VI_K_RIGHT:        // Cursor Key Right
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_right();
+               break;
+#if ENABLE_FEATURE_VI_YANKMARK
+       case '"':                       // "- name a register to use for Delete/Yank
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       YDreg = c1 - 'a';
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case '\'':                      // '- goto a specific mark
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       c1 = c1 - 'a';
+                       // get the b-o-l
+                       q = mark[(unsigned char) c1];
+                       if (text <= q && q < end) {
+                               dot = q;
+                               dot_begin();    // go to B-o-l
+                               dot_skip_over_ws();
+                       }
+               } else if (c1 == '\'') {        // goto previous context
+                       dot = swap_context(dot);        // swap current and previous context
+                       dot_begin();    // go to B-o-l
+                       dot_skip_over_ws();
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case 'm':                       // m- Mark a line
+               // this is really stupid.  If there are any inserts or deletes
+               // between text[0] and dot then this mark will not point to the
+               // correct location! It could be off by many lines!
+               // Well..., at least its quick and dirty.
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       c1 = c1 - 'a';
+                       // remember the line
+                       mark[(int) c1] = dot;
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case 'P':                       // P- Put register before
+       case 'p':                       // p- put register after
+               p = reg[YDreg];
+               if (p == 0) {
+                       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
+               }
+               dot = string_insert(dot, p);    // insert the string
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'U':                       // U- Undo; replace current line with original version
+               if (reg[Ureg] != 0) {
+                       p = begin_line(dot);
+                       q = end_line(dot);
+                       p = text_hole_delete(p, q);     // delete cur line
+                       p = string_insert(p, reg[Ureg]);        // insert orig line
+                       dot = p;
+                       dot_skip_over_ws();
+               }
+               break;
+#endif /* FEATURE_VI_YANKMARK */
+       case '$':                       // $- goto end of line
+       case VI_K_END:          // Cursor Key End
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot = end_line(dot);
+               break;
+       case '%':                       // %- find matching char of pair () [] {}
+               for (q = dot; q < end && *q != '\n'; q++) {
+                       if (strchr("()[]{}", *q) != NULL) {
+                               // we found half of a pair
+                               p = find_pair(q, *q);
+                               if (p == NULL) {
+                                       indicate_error(c);
+                               } else {
+                                       dot = p;
+                               }
+                               break;
+                       }
+               }
+               if (*q == '\n')
+                       indicate_error(c);
+               break;
+       case 'f':                       // f- forward to a user specified char
+               last_forward_char = get_one_char();     // get the search char
+               //
+               // dont separate these two commands. 'f' depends on ';'
+               //
+               //**** fall 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 (last_modifying_cmd != NULL && 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:
+               if (last_search_pattern == 0) {
+                       msg = "No previous regular expression";
+                       goto dc2;
+               }
+               if (last_search_pattern[0] == '/') {
+                       dir = FORWARD;  // assume FORWARD search
+                       p = dot + 1;
+               }
+               if (last_search_pattern[0] == '?') {
+                       dir = BACK;
+                       p = dot - 1;
+               }
+ dc4:
+               q = char_search(p, last_search_pattern + 1, dir, FULL);
+               if (q != NULL) {
+                       dot = q;        // good search, update "dot"
+                       msg = "";
+                       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 (from vim)
+                                       // (default to first line in file)
+               c1 = get_one_char();
+               if (c1 != 'g') {
+                       buf[0] = 'g';
+                       buf[1] = c1;
+                       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 VI_K_INSERT:       // Cursor Key Insert
+ dc_i:
+               cmd_mode = 1;   // start insrting
+               break;
+       case 'J':                       // J- join current and next lines together
+               if (cmdcnt-- > 2) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_end();              // move to NL
+               if (dot < end - 1) {    // make sure not last char in text[]
+                       *dot++ = ' ';   // replace NL with space
+                       file_modified++;
+                       while (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 VI_K_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 VI_K_UP:           // cursor key Up
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_prev();
+               dot = move_to_col(dot, ccol + offset);  // try stay in same col
+               break;
+       case 'r':                       // r- replace the current char with user input
+               c1 = get_one_char();    // get the replacement char
+               if (*dot != '\n') {
+                       *dot = c1;
+                       file_modified++;        // has the file been modified
+               }
+               end_cmd_q();    // stop adding to q
+               break;
+       case 't':                       // t- move to char prior to next x
+               last_forward_char = get_one_char();
+               do_cmd(';');
+               if (*dot == last_forward_char)
+                       dot_left();
+               last_forward_char= 0;
+               break;
+       case 'w':                       // w- forward a word
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
+                       dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
+               } else if (ispunct(*dot)) {     // we are on PUNCT
+                       dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
+               }
+               if (dot < end - 1)
+                       dot++;          // move over word
+               if (isspace(*dot)) {
+                       dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
+               }
+               break;
+       case 'z':                       // z-
+               c1 = get_one_char();    // get the replacement char
+               cnt = 0;
+               if (c1 == '.')
+                       cnt = (rows - 2) / 2;   // put dot at center
+               if (c1 == '-')
+                       cnt = rows - 2; // put dot at bottom
+               screenbegin = begin_line(dot);  // start dot at top
+               dot_scroll(cnt, -1);
+               break;
+       case '|':                       // |- move to column "cmdcnt"
+               dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
+               break;
+       case '~':                       // ~- flip the case of letters   a-z -> A-Z
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               if (islower(*dot)) {
+                       *dot = toupper(*dot);
+                       file_modified++;        // has the file been modified
+               } else if (isupper(*dot)) {
+                       *dot = tolower(*dot);
+                       file_modified++;        // has the file been modified
+               }
+               dot_right();
+               end_cmd_q();    // stop adding to q
+               break;
+               //----- The Cursor and Function Keys -----------------------------
+       case VI_K_HOME: // Cursor Key Home
+               dot_begin();
+               break;
+               // The Fn keys could point to do_macro which could translate them
+       case VI_K_FUN1: // Function Key F1
+       case VI_K_FUN2: // Function Key F2
+       case VI_K_FUN3: // Function Key F3
+       case VI_K_FUN4: // Function Key F4
+       case VI_K_FUN5: // Function Key F5
+       case VI_K_FUN6: // Function Key F6
+       case VI_K_FUN7: // Function Key F7
+       case VI_K_FUN8: // Function Key F8
+       case VI_K_FUN9: // Function Key F9
+       case VI_K_FUN10:        // Function Key F10
+       case VI_K_FUN11:        // Function Key F11
+       case VI_K_FUN12:        // Function Key F12
+               break;
+       }
+
+ dc1:
+       // if text[] just became empty, add back an empty line
+       if (end == text) {
+               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(0, 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..03a1a85
--- /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 $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.pl b/examples/depmod.pl
new file mode 100755 (executable)
index 0000000..c356d27
--- /dev/null
@@ -0,0 +1,292 @@
+#!/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;
+use File::Find;
+use strict;
+
+# Set up some default values
+my $kdir="";
+my $basedir="";
+my $kernel="";
+my $kernelsyms="";
+my $symprefix="";
+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
+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,
+);
+
+die $usage if $help;
+die $usage unless $basedir && ( $kernel || $kernelsyms );
+die "can't use both -k and -F\n\n$usage" if $kernel && $kernelsyms;
+
+# Strip any trailing or multiple slashes from basedir
+$basedir =~ s-(/)\1+-/-g;
+
+# The base directory should contain /lib/modules somewhere
+if($basedir !~ m-/lib/modules-) {
+    warn "WARNING: base directory does not match ..../lib/modules\n";
+}
+
+# if no kernel version is contained in the basedir, try to find one
+if($basedir !~ m-/lib/modules/\d\.\d-) {
+    opendir(BD, $basedir) or die "can't open basedir $basedir : $!\n";
+    foreach ( readdir(BD) ) {
+        next if /^\.\.?$/;
+        next unless -d "$basedir/$_";
+        warn "dir = $_\n" if $verbose;
+        if( /^\d\.\d/ ) {
+            $kdir = $_;
+            warn("Guessed module directory as $basedir/$kdir\n");
+            last;
+        }
+    }
+    closedir(BD);
+    die "Cannot find a kernel version under $basedir\n" unless $kdir;
+    $basedir = "$basedir/$kdir";
+}
+
+# Find the list of .o or .ko files living under $basedir
+warn "**** Locating all modules\n" if $verbose;
+find sub {
+    my $file;
+       if ( -f $_  && ! -d $_ ) {
+               $file = $File::Find::name;
+               if ( $file =~ /\.k?o$/ ) {
+                       push(@liblist, $file);
+                       warn "$file\n" if $verbose;
+               }
+       }
+}, $basedir;
+warn "**** Finished locating modules\n" if $verbose;
+
+foreach my $obj ( @liblist ){
+    # turn the input file name into a target tag name
+    my ($tgtname) = $obj =~ m-(/lib/modules/.*)$-;
+
+    warn "\nMODULE = $tgtname\n" if $verbose;
+
+    # get a list of symbols
+       my @output=`$nm $obj`;
+
+    build_ref_tables($tgtname, \@output, $exp, $dep);
+}
+
+
+# vmlinux is a special name that is only used to resolve symbols
+my $tgtname = 'vmlinux';
+my @output = $kernelsyms ? `cat $kernelsyms` : `$nm $kernel`;
+warn "\nMODULE = $tgtname\n" if $verbose;
+build_ref_tables($tgtname, \@output, $exp, $dep);
+
+# resolve the dependencies for each module
+# reduce dependencies: remove unresolvable and resolved from vmlinux/System.map
+# remove duplicates
+foreach my $module (keys %$dep) {
+    warn "reducing module: $module\n" if $verbose;
+    $mod->{$module} = {};
+    foreach (@{$dep->{$module}}) {
+        if( $exp->{$_} ) {
+            warn "resolved symbol $_ in file $exp->{$_}\n" if $verbose;
+            next if $exp->{$_} =~ /vmlinux/;
+            $mod->{$module}{$exp->{$_}} = 1;
+        } else {
+            warn "unresolved symbol $_ in file $module\n";
+        }
+    }
+}
+
+# figure out where the output should go
+if ($stdout == 0) {
+    open(STDOUT, ">$basedir/modules.dep")
+                             or die "cannot open $basedir/modules.dep: $!";
+}
+my $kseries = $basedir =~ m,/2\.6\.[^/]*, ? '2.6' : '2.4';
+
+foreach my $module ( keys %$mod ) {
+    if($kseries eq '2.4') {
+           print "$module:\t";
+           my @sorted = sort bydep keys %{$mod->{$module}};
+           print join(" \\\n\t",@sorted);
+           print "\n\n";
+    } else {
+           print "$module: ";
+           my @sorted = sort bydep keys %{$mod->{$module}};
+           print join(" ",@sorted);
+           print "\n";
+    }
+}
+
+
+sub build_ref_tables
+{
+    my ($name, $sym_ar, $exp, $dep) = @_;
+
+       my $ksymtab = grep m/ __ksymtab/, @$sym_ar;
+
+    # gather the exported symbols
+       if($ksymtab){
+        # explicitly exported
+        foreach ( @$sym_ar ) {
+            / __ksymtab_(.*)$/ and do {
+                warn "sym = $1\n" if $verbose;
+                $exp->{$1} = $name;
+            };
+        }
+       } else {
+        # exporting all symbols
+        foreach ( @$sym_ar ) {
+            / [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 ) {
+        !/ __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..50415cb
--- /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 (-xdev) option"
+       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 direcotries.
+         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..f75bc9e
--- /dev/null
@@ -0,0 +1,908 @@
+/* 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 ATTRIBUTE_UNUSED, \
+                                                  struct stat *statbuf ATTRIBUTE_UNUSED, \
+                                                  action_##name* ap ATTRIBUTE_UNUSED)
+                         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 fileAction(const char *fileName,
+               struct stat *statbuf,
+               void *userData SKIP_FEATURE_FIND_MAXDEPTH(ATTRIBUTE_UNUSED),
+               int depth SKIP_FEATURE_FIND_MAXDEPTH(ATTRIBUTE_UNUSED))
+{
+       int i;
+#if ENABLE_FEATURE_FIND_MAXDEPTH
+       int maxdepth = (int)(ptrdiff_t)userData;
+
+       if (depth > maxdepth) return SKIP;
+#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("-maxdepth\0")
+                         ;
+       enum {
+                         OPT_FOLLOW,
+USE_FEATURE_FIND_XDEV(    OPT_XDEV    ,)
+USE_FEATURE_FIND_MAXDEPTH(OPT_MAXDEPTH,)
+       };
+
+       char *arg;
+       char **argp;
+       int i, firstopt, status = EXIT_SUCCESS;
+#if ENABLE_FEATURE_FIND_MAXDEPTH
+       int maxdepth = INT_MAX;
+#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_MAXDEPTH) {
+                       if (!argp[1])
+                               bb_show_usage();
+                       maxdepth = 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
+                               /* double cast suppresses
+                                * "cast to ptr from int of different size" */
+                               (void*)(ptrdiff_t)maxdepth,/* 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..259026e
--- /dev/null
@@ -0,0 +1,550 @@
+/* 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") \
+       "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(0) 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 */
+       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,
+};
+
+#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)
+
+struct globals {
+       int max_matches;
+       int reflags;
+       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;
+       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         )
+#define reflags           (G.reflags             )
+#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 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;
+       regex_t preg;
+#define PATTERN_MEM_A 1
+#define COMPILED 2
+       int flg_mem_alocated_compiled;
+} grep_list_data_t;
+
+
+static void print_line(const char *line, int linenum, char decoration)
+{
+#if ENABLE_FEATURE_GREP_CONTEXT
+       /* 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))
+               puts(line);
+}
+
+static int grep_file(FILE *file)
+{
+       char *line;
+       smalluint found;
+       int linenum = 0;
+       int nmatches = 0;
+       regmatch_t regmatch;
+#if ENABLE_FEATURE_GREP_CONTEXT
+       int print_n_lines_after = 0;
+       int curpos = 0; /* track where we are in the circular 'before' buffer */
+       int idx = 0; /* used for iteration through the circular buffer */
+#else
+       enum { print_n_lines_after = 0 };
+#endif /* ENABLE_FEATURE_GREP_CONTEXT */
+
+       while ((line = xmalloc_getline(file)) != NULL) {
+               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;
+                                       xregcomp(&(gl->preg), gl->pattern, reflags);
+                               }
+                               regmatch.rm_so = 0;
+                               regmatch.rm_eo = 0;
+                               if (regexec(&(gl->preg), line, 1, &regmatch, 0) == 0) {
+                                       if (!(option_mask32 & OPT_w))
+                                               found = 1;
+                                       else {
+                                               char c = ' ';
+                                               if (regmatch.rm_so)
+                                                       c = line[regmatch.rm_so - 1];
+                                               if (!isalnum(c) && c != '_') {
+                                                       c = line[regmatch.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(0);
+                               }
+                               /* 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], 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, linenum, ':');
+                                       } else {
+                                               line[regmatch.rm_eo] = '\0';
+                                               print_line(line + regmatch.rm_so, linenum, ':');
+                                       }
+                               } else {
+                                       print_line(line, linenum, ':');
+                               }
+                       }
+               }
+#if ENABLE_FEATURE_GREP_CONTEXT
+               else { /* no match */
+                       /* if we need to print some context lines after the last match, do so */
+                       if (print_n_lines_after) {
+                               print_line(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;
+                               curpos = (curpos + 1) % lines_before;
+                               /* avoid free(line) - we took the line */
+                               line = NULL;
+                       }
+               }
+
+#endif /* ENABLE_FEATURE_GREP_CONTEXT */
+               free(line);
+
+               /* Did we print all context after last requested match? */
+               if ((option_mask32 & OPT_m)
+                && !print_n_lines_after && nmatches == max_matches)
+                       break;
+       }
+
+       /* 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(ffile, "r");
+               while ((line = xmalloc_getline(f)) != NULL) {
+                       llist_add_to(&pattern_head,
+                               new_grep_list_data(line, PATTERN_MEM_A));
+               }
+       }
+}
+
+static int file_action_grep(const char *filename,
+                       struct stat *statbuf ATTRIBUTE_UNUSED,
+                       void* matched,
+                       int depth ATTRIBUTE_UNUSED)
+{
+       FILE *file = fopen(filename, "r");
+       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;
+               //option_mask32 |= OPT_A|OPT_B; /* for parser */
+       }
+       /* 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(char *));
+#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 (!(option_mask32 & (OPT_o | OPT_w)))
+               reflags = REG_NOSUB;
+
+       if (ENABLE_FEATURE_GREP_EGREP_ALIAS
+        && (applet_name[0] == 'e' || (option_mask32 & OPT_E))
+       ) {
+               reflags |= REG_EXTENDED;
+       }
+
+       if (option_mask32 & OPT_i)
+               reflags |= REG_ICASE;
+
+       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[(optind)..(argc-1)] should be names of file to grep through. If
+        * there is more than one file to grep, we will print the filenames. */
+       if (argc > 1)
+               print_filename = 1;
+       /* -H / -h of course override */
+       if (option_mask32 & 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(cur_file, "r");
+                       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 & PATTERN_MEM_A))
+                               free(gl->pattern);
+                       if ((gl->flg_mem_alocated_compiled & COMPILED))
+                               regfree(&(gl->preg));
+                       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..ee16ea6
--- /dev/null
@@ -0,0 +1,527 @@
+/* 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 ((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(CURRENT_TTY, "r");
+       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 ATTRIBUTE_UNUSED, size_t mc, char *buf)
+{
+       int c;                  /* current char */
+       char *s = NULL;         /* start word */
+       char *p = NULL;         /* pointer to end word */
+       size_t line_l = 0;      /* size loaded args line */
+       xlist_t *cur;
+       xlist_t *prev;
+
+       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,
+       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 ,
+       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::" \
+       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 = "_";
+       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);
+
+       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;
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(max_chars);
+       return child_error;
+}
+
+
+#ifdef TEST
+
+const char *applet_name = "debug stuff usage";
+
+void bb_show_usage(void)
+{
+       fprintf(stderr, "Usage: %s [-p] [-r] [-t] -[x] [-n max_arg] [-s max_chars]\n",
+               applet_name);
+       exit(1);
+}
+
+int main(int argc, char **argv)
+{
+       return xargs_main(argc, argv);
+}
+#endif /* TEST */
diff --git a/include/applets.h b/include/applets.h
new file mode 100644 (file)
index 0000000..13c4648
--- /dev/null
@@ -0,0 +1,407 @@
+/* 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_NOUSAGE(name,main,l,s)       int main##_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_NOUSAGE(name,main,l,s)       name main##_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 "\n\n" name##_full_usage "\0"
+# define APPLET_NOUSAGE(name,main,l,s)       "\b\0"
+# define APPLET_ODDNAME(name,main,l,s,name2) name2##_trivial_usage "\n\n" name2##_full_usage "\0"
+# define APPLET_NOEXEC(name,main,l,s,name2)  name2##_trivial_usage "\n\n" name2##_full_usage "\0"
+# define APPLET_NOFORK(name,main,l,s,name2)  name2##_trivial_usage "\n\n" name2##_full_usage "\0"
+
+#elif defined(MAKE_USAGE) && !ENABLE_FEATURE_VERBOSE_USAGE
+# define APPLET(name,l,s)                    name##_trivial_usage "\0"
+# define APPLET_NOUSAGE(name,main,l,s)       "\b\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_NOUSAGE(name,main,l,s)       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_NOUSAGE(name,main,l,s)       { #name, #main, 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_NOUSAGE([[, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_ADDGROUP(APPLET(addgroup, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_ADDUSER(APPLET(adduser, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_ADJTIMEX(APPLET(adjtimex, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_AR(APPLET(ar, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_ARP(APPLET(arp, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_ARPING(APPLET(arping, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_ASH(APPLET_NOUSAGE(ash, ash, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_AWK(APPLET_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_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_NOUSAGE(cttyhack, 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_DEVFSD(APPLET(devfsd, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_DF(APPLET(df, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_APP_DHCPRELAY(APPLET(dhcprelay, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_DIFF(APPLET(diff, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_DIRNAME(APPLET_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_NOUSAGE(e2label, tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER))
+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_NOUSAGE(egrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_EJECT(APPLET(eject, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_ENV(APPLET(env, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_ENVDIR(APPLET_ODDNAME(envdir, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, envdir))
+USE_ENVUIDGID(APPLET_ODDNAME(envuidgid, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, envuidgid))
+USE_ETHER_WAKE(APPLET_ODDNAME(ether-wake, ether_wake, _BB_DIR_USR_BIN, _BB_SUID_NEVER, ether_wake))
+USE_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_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_FETCHMAIL(APPLET_ODDNAME(fetchmail, sendgetmail, _BB_DIR_USR_BIN, _BB_SUID_NEVER, fetchmail))
+USE_FEATURE_GREP_FGREP_ALIAS(APPLET_NOUSAGE(fgrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_FIND(APPLET_NOEXEC(find, find, _BB_DIR_USR_BIN, _BB_SUID_NEVER, find))
+USE_FINDFS(APPLET(findfs, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_FOLD(APPLET(fold, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_FREE(APPLET(free, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_FREERAMDISK(APPLET(freeramdisk, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_FSCK(APPLET(fsck, _BB_DIR_SBIN, _BB_SUID_NEVER))
+//USE_E2FSCK(APPLET_NOUSAGE(fsck.ext2, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER))
+//USE_E2FSCK(APPLET_NOUSAGE(fsck.ext3, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_FSCK_MINIX(APPLET_ODDNAME(fsck.minix, fsck_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, fsck_minix))
+USE_FTPGET(APPLET_ODDNAME(ftpget, ftpgetput, _BB_DIR_USR_BIN, _BB_SUID_NEVER, ftpget))
+USE_FTPPUT(APPLET_ODDNAME(ftpput, ftpgetput, _BB_DIR_USR_BIN, _BB_SUID_NEVER, ftpput))
+USE_FUSER(APPLET(fuser, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_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_ODDNAME(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_NOUSAGE(hush, hush, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_HWCLOCK(APPLET(hwclock, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_ID(APPLET(id, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_IFCONFIG(APPLET(ifconfig, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_IFUPDOWN(APPLET_ODDNAME(ifdown, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifdown))
+USE_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_INSMOD(APPLET(insmod, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_INSTALL(APPLET(install, _BB_DIR_USR_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_NOUSAGE(linux32, setarch, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_SETARCH(APPLET_NOUSAGE(linux64, setarch, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_FEATURE_INITRD(APPLET_NOUSAGE(linuxrc, init, _BB_DIR_ROOT, _BB_SUID_NEVER))
+USE_LN(APPLET_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_BIN, _BB_SUID_NEVER))
+USE_LOADKMAP(APPLET(loadkmap, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_LOGGER(APPLET(logger, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_LOGIN(APPLET(login, _BB_DIR_BIN, _BB_SUID_ALWAYS))
+USE_LOGNAME(APPLET_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_UNLZMA(APPLET_ODDNAME(lzmacat, unlzma, _BB_DIR_USR_BIN, _BB_SUID_NEVER, lzmacat))
+USE_MAKEDEVS(APPLET(makedevs, _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_MKE2FS(APPLET(mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MKFIFO(APPLET(mkfifo, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+//USE_MKE2FS(APPLET_NOUSAGE(mkfs.ext2, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER))
+//USE_MKE2FS(APPLET_NOUSAGE(mkfs.ext3, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MKFS_MINIX(APPLET_ODDNAME(mkfs.minix, mkfs_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_minix))
+USE_MKNOD(APPLET(mknod, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_MKSWAP(APPLET(mkswap, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MKTEMP(APPLET(mktemp, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_MODPROBE(APPLET(modprobe, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_MORE(APPLET(more, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_MOUNT(APPLET(mount, _BB_DIR_BIN, USE_DESKTOP(_BB_SUID_MAYBE) SKIP_DESKTOP(_BB_SUID_NEVER)))
+USE_MOUNTPOINT(APPLET(mountpoint, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_MSH(APPLET_NOUSAGE(msh, msh, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_MT(APPLET(mt, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_MV(APPLET(mv, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_NAMEIF(APPLET(nameif, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_NC(APPLET(nc, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_NETSTAT(APPLET(netstat, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_NICE(APPLET(nice, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_NMETER(APPLET(nmeter, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_NOHUP(APPLET(nohup, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_NSLOOKUP(APPLET(nslookup, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_OD(APPLET(od, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_OPENVT(APPLET(openvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_PASSWD(APPLET(passwd, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS))
+USE_PATCH(APPLET(patch, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_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_NEVER))
+USE_PIPE_PROGRESS(APPLET_NOUSAGE(pipe_progress, pipe_progress, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_PIVOT_ROOT(APPLET(pivot_root, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_PKILL(APPLET_ODDNAME(pkill, pgrep, _BB_DIR_USR_BIN, _BB_SUID_NEVER, pkill))
+USE_HALT(APPLET_ODDNAME(poweroff, halt, _BB_DIR_SBIN, _BB_SUID_NEVER, poweroff))
+USE_PRINTENV(APPLET(printenv, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_PRINTF(APPLET(printf, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_PS(APPLET(ps, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_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_READAHEAD(APPLET(readahead, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_READLINK(APPLET(readlink, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_READPROFILE(APPLET(readprofile, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
+USE_REALPATH(APPLET(realpath, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_HALT(APPLET_ODDNAME(reboot, halt, _BB_DIR_SBIN, _BB_SUID_NEVER, reboot))
+USE_RENICE(APPLET(renice, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_RESET(APPLET(reset, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_RESIZE(APPLET(resize, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_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_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_ODDNAME(sendmail, sendgetmail, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sendmail))
+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_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_NOUSAGE(sh, ash, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_FEATURE_SH_IS_HUSH(APPLET_NOUSAGE(sh, hush, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_FEATURE_SH_IS_MSH(APPLET_NOUSAGE(sh, msh, _BB_DIR_BIN, _BB_SUID_NEVER))
+USE_SHA1SUM(APPLET_ODDNAME(sha1sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sha1sum))
+USE_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_BIN, _BB_SUID_NEVER))
+USE_SWAPONOFF(APPLET_ODDNAME(swapoff, swap_on_off, _BB_DIR_SBIN, _BB_SUID_NEVER,swapoff))
+USE_SWAPONOFF(APPLET_ODDNAME(swapon, swap_on_off, _BB_DIR_SBIN, _BB_SUID_NEVER, swapon))
+USE_SWITCH_ROOT(APPLET(switch_root, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_SYNC(APPLET_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_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_NOEXEC(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_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_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_NOUSAGE
+#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..cad45ac
--- /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        _BB_INTERNAL_H_
+#define        _BB_INTERNAL_H_    1
+
+#include "libbb.h"
+
+/* 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.
+ */
+void lbb_prepare(const char *applet
+       USE_FEATURE_INDIVIDUAL(, char **argv)
+       ) MAIN_EXTERNALLY_VISIBLE;
+#if ENABLE_BUILD_LIBBUSYBOX
+#if ENABLE_FEATURE_SHARED_BUSYBOX
+int lbb_main(char **argv) EXTERNALLY_VISIBLE;
+#else
+int lbb_main(char **argv);
+#endif
+#endif
+
+
+#endif /* _BB_INTERNAL_H_ */
diff --git a/include/dump.h b/include/dump.h
new file mode 100644 (file)
index 0000000..7e17154
--- /dev/null
@@ -0,0 +1,50 @@
+/* vi: set sw=4 ts=4: */
+#define        F_IGNORE        0x01            /* %_A */
+#define        F_SETREP        0x02            /* rep count set, not default */
+#define        F_ADDRESS       0x001           /* print offset */
+#define        F_BPAD          0x002           /* blank pad */
+#define        F_C             0x004           /* %_c */
+#define        F_CHAR          0x008           /* %c */
+#define        F_DBL           0x010           /* %[EefGf] */
+#define        F_INT           0x020           /* %[di] */
+#define        F_P             0x040           /* %_p */
+#define        F_STR           0x080           /* %s */
+#define        F_U             0x100           /* %_u */
+#define        F_UINT          0x200           /* %[ouXx] */
+#define        F_TEXT          0x400           /* no conversions */
+
+enum _vflag { ALL, DUP, FIRST, WAIT }; /* -v values */
+
+typedef struct _pr {
+       struct _pr *nextpr;             /* next print unit */
+       unsigned int flags;                     /* flag values */
+       int bcnt;                       /* byte count */
+       char *cchar;                    /* conversion character */
+       char *fmt;                      /* printf format */
+       char *nospace;                  /* no whitespace version */
+} PR;
+
+typedef struct _fu {
+       struct _fu *nextfu;             /* next format unit */
+       struct _pr *nextpr;             /* next print unit */
+       unsigned int flags;                     /* flag values */
+       int reps;                       /* repetition count */
+       int bcnt;                       /* byte count */
+       char *fmt;                      /* format string */
+} FU;
+
+typedef struct _fs {                   /* format strings */
+       struct _fs *nextfs;             /* linked list of format strings */
+       struct _fu *nextfu;             /* linked list of format units */
+       int bcnt;
+} FS;
+
+extern void bb_dump_add(const char *fmt);
+extern int bb_dump_dump(char **argv);
+extern int bb_dump_size(FS * fs);
+
+extern FS *bb_dump_fshead;             /* head of format strings */
+extern int bb_dump_blocksize;                          /* data block size */
+extern int bb_dump_length;                     /* max bytes to read */
+extern enum _vflag bb_dump_vflag;
+extern off_t bb_dump_skip;                      /* bytes to skip */
diff --git a/include/grp_.h b/include/grp_.h
new file mode 100644 (file)
index 0000000..061b86e
--- /dev/null
@@ -0,0 +1,133 @@
+/* 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>
+ */
+
+#if !ENABLE_USE_BB_PWD_GRP
+
+#include <grp.h>
+
+#else
+
+#ifndef        _GRP_H
+#define        _GRP_H 1
+
+/* The group structure.         */
+struct group {
+       char *gr_name;          /* Group name.  */
+       char *gr_passwd;        /* Password.    */
+       gid_t gr_gid;           /* Group ID.    */
+       char **gr_mem;          /* Member list. */
+};
+
+/* We don't reimplement this, just supplying prototype */
+/* The function itself is in libc */
+/* Set the group set for the current user to GROUPS (N of them).  */
+extern int setgroups(size_t __n, __const gid_t *__groups);
+
+
+#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.
+ * In theory it isn't necessary, but I saw weird interactions at link time.
+ * Let's play safe */
+
+
+/* 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);
+
+
+#endif /* grp.h  */
+#endif
diff --git a/include/inet_common.h b/include/inet_common.h
new file mode 100644 (file)
index 0000000..eb4cb73
--- /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);
+
+/* numeric: & 0x8000: "default" instead of "*",
+ *          & 0x4000: host instead of net,
+ *          & 0x0fff: don't resolve
+ */
+
+int INET6_resolve(const char *name, struct sockaddr_in6 *sin6);
+
+/* These return malloced string */
+char *INET_rresolve(struct sockaddr_in *s_in, int numeric, uint32_t netmask);
+char *INET6_rresolve(struct sockaddr_in6 *sin6, int numeric);
diff --git a/include/libbb.h b/include/libbb.h
new file mode 100644 (file)
index 0000000..859b3bc
--- /dev/null
@@ -0,0 +1,1339 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Busybox main internal header file
+ *
+ * Based in part on code from sash, Copyright (c) 1999 by David I. Bell
+ * Permission has been granted to redistribute this code under the GPL.
+ *
+ * Licensed under the GPL version 2, see the file LICENSE in this tarball.
+ */
+#ifndef        __LIBBUSYBOX_H__
+#define        __LIBBUSYBOX_H__    1
+
+#include "platform.h"
+
+#include <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
+
+#include "pwd_.h"
+#include "grp_.h"
+/* ifdef it out, because it may include <shadow.h> */
+/* and we may not even _have_ <shadow.h>! */
+#if ENABLE_FEATURE_SHADOWPASSWDS
+#include "shadow_.h"
+#endif
+
+/* Some libc's don't declare it, help them */
+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);
+
+
+/* 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 */
+#  define XATOOFF(a) xatoul_range(a, 0, LONG_MAX)
+/* usage: sz = BB_STRTOOFF(s, NULL, 10); if (errno || sz < 0) die(); */
+#  define BB_STRTOOFF bb_strtoul
+#  define STRTOOFF strtoul
+/* usage: printf("size: %"OFF_FMT"d (%"OFF_FMT"x)\n", sz, sz); */
+#  define OFF_FMT "l"
+# else
+/* "long" is too short, need "long long" */
+#  define XATOOFF(a) xatoull_range(a, 0, LLONG_MAX)
+#  define BB_STRTOOFF bb_strtoull
+#  define STRTOOFF strtoull
+#  define OFF_FMT "ll"
+# endif
+#else
+/* CONFIG_LFS is off */
+# if UINT_MAX == 0xffffffff
+/* While sizeof(off_t) == sizeof(int), off_t is typedef'ed to long anyway.
+ * gcc will throw warnings on printf("%d", off_t). Crap... */
+#  define XATOOFF(a) xatoi_u(a)
+#  define BB_STRTOOFF bb_strtou
+#  define STRTOOFF strtol
+#  define OFF_FMT "l"
+# else
+#  define XATOOFF(a) xatoul_range(a, 0, LONG_MAX)
+#  define BB_STRTOOFF bb_strtoul
+#  define STRTOOFF strtol
+#  define OFF_FMT "l"
+# endif
+#endif
+/* scary. better ideas? (but do *test* them first!) */
+#define OFF_T_MAX  ((off_t)~((off_t)1 << (sizeof(off_t)*8-1)))
+
+/* Some useful definitions */
+#undef FALSE
+#define FALSE   ((int) 0)
+#undef TRUE
+#define TRUE    ((int) 1)
+#undef SKIP
+#define SKIP   ((int) 2)
+
+/* for mtab.c */
+#define MTAB_GETMOUNTPT '1'
+#define MTAB_GETDEVICE  '2'
+
+#define BUF_SIZE        8192
+#define EXPAND_ALLOC    1024
+
+/* Macros for min/max.  */
+#ifndef MIN
+#define        MIN(a,b) (((a)<(b))?(a):(b))
+#endif
+
+#ifndef MAX
+#define        MAX(a,b) (((a)>(b))?(a):(b))
+#endif
+
+/* buffer allocation schemes */
+#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_us(void);
+unsigned monotonic_sec(void);
+
+extern void chomp(char *s);
+extern void trim(char *s);
+extern char *skip_whitespace(const char *);
+extern char *skip_non_whitespace(const char *);
+
+//TODO: supply a pointer to char[11] buffer (avoid statics)?
+extern const char *bb_mode_string(mode_t mode);
+extern int is_directory(const char *name, int followLinks, struct stat *statBuf);
+extern int remove_file(const char *path, int flags);
+extern int copy_file(const char *source, const char *dest, int flags);
+enum {
+       ACTION_RECURSE        = (1 << 0),
+       ACTION_FOLLOWLINKS    = (1 << 1),
+       ACTION_FOLLOWLINKS_L0 = (1 << 2),
+       ACTION_DEPTHFIRST     = (1 << 3),
+       /*ACTION_REVERSE      = (1 << 4), - unused */
+};
+extern int recursive_action(const char *fileName, unsigned flags,
+       int (*fileAction) (const char *fileName, struct stat* statbuf, void* userData, int depth),
+       int (*dirAction) (const char *fileName, struct stat* statbuf, void* userData, int depth),
+       void* userData, unsigned depth);
+extern int device_open(const char *device, int mode);
+enum { GETPTY_BUFSIZE = 16 }; /* more than enough for "/dev/ttyXXX" */
+extern int getpty(char *line);
+extern int get_console_fd(void);
+extern char *find_block_device(const char *path);
+/* bb_copyfd_XX print read/write errors and return -1 if they occur */
+extern off_t bb_copyfd_eof(int fd1, int fd2);
+extern off_t bb_copyfd_size(int fd1, int fd2, off_t size);
+extern void bb_copyfd_exact_size(int fd1, int fd2, off_t size);
+/* "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) ATTRIBUTE_NORETURN;
+extern char bb_process_escape_sequence(const char **ptr);
+/* 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);
+/* "abc/def/" -> "" and it never modifies 'path' */
+extern char *bb_get_last_path_component_nostrip(const char *path);
+
+int ndelay_on(int fd);
+int ndelay_off(int fd);
+int close_on_exec_on(int fd);
+void xdup2(int, int);
+void xmove_fd(int, int);
+
+
+DIR *xopendir(const char *path);
+DIR *warn_opendir(const char *path);
+
+/* UNUSED: char *xmalloc_realpath(const char *path); */
+char *xmalloc_readlink(const char *path);
+char *xmalloc_readlink_or_warn(const char *path);
+char *xrealloc_getcwd_or_warn(char *cwd);
+
+char *xmalloc_follow_symlinks(const char *path);
+
+
+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
+        */
+       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));
+/* 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: */
+void bb_signals_recursive(int sigs, void (*f)(int));
+/* syscalls like read() will be interrupted with EINTR: */
+void signal_no_SA_RESTART_empty_mask(int sig, void (*handler)(int));
+/* syscalls like read() won't be interrupted (though select/poll will be): */
+void signal_SA_RESTART_empty_mask(int sig, void (*handler)(int));
+void wait_for_any_sig(void);
+void kill_myself_with_sig(int sig) ATTRIBUTE_NORETURN;
+void sig_block(int sig);
+void sig_unblock(int sig);
+/* Will do sigaction(signum, act, NULL): */
+int sigaction_set(int sig, const struct sigaction *act);
+/* SIG_BLOCK/SIG_UNBLOCK all signals: */
+int sigprocmask_allsigs(int how);
+
+
+void xsetgid(gid_t gid);
+void xsetuid(uid_t uid);
+void xchdir(const char *path);
+void xchroot(const char *path);
+void xsetenv(const char *key, const char *value);
+void xunlink(const char *pathname);
+void xstat(const char *pathname, struct stat *buf);
+int xopen(const char *pathname, int flags);
+int xopen3(const char *pathname, int flags, int mode);
+int open_or_warn(const char *pathname, int flags);
+int open3_or_warn(const char *pathname, int flags, int mode);
+int open_or_warn_stdin(const char *pathname);
+void xrename(const char *oldpath, const char *newpath);
+int rename_or_warn(const char *oldpath, const char *newpath);
+off_t xlseek(int fd, off_t offset, int whence);
+off_t fdlength(int fd);
+
+void xpipe(int filedes[2]);
+/* 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);
+void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
+void xlisten(int s, int backlog);
+void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen);
+ssize_t xsendto(int s, const void *buf, size_t len, const struct sockaddr *to,
+                               socklen_t tolen);
+/* 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); /* On Linux this never fails. */
+int setsockopt_broadcast(int fd);
+/* NB: returns port in host byte order */
+unsigned bb_lookup_port(const char *port, const char *protocol, unsigned default_port);
+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);
+#else
+int xsocket_type(len_and_sockaddr **lsap, int sock_type);
+#define xsocket_type(lsap, af, sock_type) xsocket_type((lsap), (sock_type))
+#endif
+int xsocket_stream(len_and_sockaddr **lsap);
+/* 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);
+int create_and_bind_dgram_or_die(const char *bindaddr, int port);
+/* 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);
+/* Connect to peer identified by lsa */
+int xconnect_stream(const len_and_sockaddr *lsa);
+/* 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);
+/* Version which dies on error */
+len_and_sockaddr* xhost2sockaddr(const char *host, int port);
+len_and_sockaddr* xdotted2sockaddr(const char *host, int port);
+/* 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);
+len_and_sockaddr* xhost_and_af2sockaddr(const char *host, int port, sa_family_t af);
+#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);
+/* Retrieve sin[6]_port or return -1 for non-INET[6] lsa's */
+int get_nport(const struct sockaddr *sa);
+/* Reverse DNS. Returns NULL on failure. */
+char* xmalloc_sockaddr2host(const struct sockaddr *sa);
+/* This one doesn't append :PORTNUM */
+char* xmalloc_sockaddr2host_noport(const struct sockaddr *sa);
+/* This one also doesn't fall back to dotted IP (returns NULL) */
+char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa);
+/* inet_[ap]ton on steroids */
+char* xmalloc_sockaddr2dotted(const struct sockaddr *sa);
+char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa);
+// "old" (ipv4 only) API
+// users: traceroute.c hostname.c - use _list_ of all IPs
+struct hostent *xgethostbyname(const char *name);
+// Also mount.c and inetd.c are using gethostbyname(),
+// + inet_common.c has additional IPv4-only stuff
+
+
+void socket_want_pktinfo(int fd);
+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);
+ssize_t recv_from_to(int fd, void *buf, size_t len, int flags,
+               struct sockaddr *from,
+               struct sockaddr *to,
+               socklen_t sa_size);
+
+char *xstrdup(const char *s);
+char *xstrndup(const char *s, int n);
+char *safe_strncpy(char *dst, const char *src, size_t size);
+/* 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);
+char *xasprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
+/* 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);
+// 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.  */
+extern void *malloc_or_warn(size_t size);
+extern void *xmalloc(size_t size);
+extern void *xzalloc(size_t size);
+extern void *xrealloc(void *old, size_t size);
+
+extern ssize_t safe_read(int fd, void *buf, size_t count);
+extern ssize_t nonblock_safe_read(int fd, void *buf, size_t count);
+// 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);
+extern void xread(int fd, void *buf, size_t count);
+extern unsigned char xread_char(int fd);
+// Read one line a-la fgets. Uses one read(), works only on seekable streams
+extern char *reads(int fd, char *buf, size_t count);
+// Read one line a-la fgets. 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);
+extern ssize_t read_close(int fd, void *buf, size_t count);
+extern ssize_t open_read_close(const char *filename, void *buf, size_t count);
+extern void *xmalloc_open_read_close(const char *filename, size_t *sizep);
+
+extern ssize_t safe_write(int fd, const void *buf, size_t count);
+// 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);
+extern void xwrite(int fd, const void *buf, size_t count);
+
+/* Reads and prints to stdout till eof, then closes FILE. Exits on error: */
+extern void xprint_and_close_file(FILE *file);
+/* Reads up to (and including) TERMINATING_STRING: */
+extern char *xmalloc_fgets_str(FILE *file, const char *terminating_string);
+/* Chops off TERMINATING_STRING: from the end: */
+extern char *xmalloc_fgetline_str(FILE *file, const char *terminating_string);
+/* Reads up to (and including) "\n" or NUL byte */
+extern char *xmalloc_fgets(FILE *file);
+/* Chops off '\n' from the end, unlike fgets: */
+extern char *xmalloc_getline(FILE *file);
+extern char *bb_get_chunk_from_file(FILE *file, int *end);
+extern void die_if_ferror(FILE *file, const char *msg);
+extern void die_if_ferror_stdout(void);
+extern void xfflush_stdout(void);
+extern void fflush_stdout_and_exit(int retval) ATTRIBUTE_NORETURN;
+extern int fclose_if_not_stdin(FILE *file);
+extern FILE *xfopen(const char *filename, const char *mode);
+/* Prints warning to stderr and returns NULL on failure: */
+extern FILE *fopen_or_warn(const char *filename, const char *mode);
+/* "Opens" stdin if filename is special, else just opens file: */
+extern FILE *xfopen_stdin(const char *filename);
+extern FILE *fopen_or_warn_stdin(const char *filename);
+
+int bb_pstrcmp(const void *a, const void *b);
+void qsort_string_vector(char **sv, unsigned count);
+
+/* 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);
+
+char *safe_gethostname(void);
+
+/* Convert each alpha char in str to lower-case */
+char* str_tolower(char *str);
+
+char *utoa(unsigned n);
+char *itoa(int n);
+/* Returns a pointer past the formatted number, does NOT null-terminate */
+char *utoa_to_buf(unsigned n, char *buf, unsigned buflen);
+char *itoa_to_buf(int n, char *buf, unsigned buflen);
+/* Intelligent formatters of bignums */
+void smart_ulltoa4(unsigned long long ul, char buf[5], const char *scale);
+void smart_ulltoa5(unsigned long long ul, char buf[5], const char *scale);
+//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);
+/* Put a string of hex bytes ("1b2e66fe"...), return advanced pointer */
+char *bin2hex(char *buf, const char *cp, int count);
+
+/* 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);
+/* Useful for reading port numbers */
+uint16_t xatou16(const char *numstr);
+
+
+/* These parse entries in /etc/passwd and /etc/group.  This is desirable
+ * for BusyBox since we want to avoid using the glibc NSS stuff, which
+ * increases target size and is often not needed on embedded systems.  */
+long xuname2uid(const char *name);
+long xgroup2gid(const char *name);
+/* wrapper: allows string to contain numeric uid or gid */
+unsigned long get_ug_id(const char *s, long (*xname2id)(const char *));
+/* 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);
+/* chown-like handling of "user[:[group]" */
+void parse_chown_usergroup_or_die(struct bb_uidgid_t *u, char *user_group);
+/* bb_getpwuid, bb_getgrgid:
+ * bb_getXXXid(buf, bufsz, id) - copy user/group name or id
+ *              as a string to buf, return user/group name or NULL
+ * bb_getXXXid(NULL, 0, id) - return user/group name or NULL
+ * bb_getXXXid(NULL, -1, id) - return user/group name or exit
+*/
+char *bb_getpwuid(char *name, int bufsize, long uid);
+char *bb_getgrgid(char *group, int bufsize, long gid);
+/* versions which cache results (useful for ps, ls etc) */
+const char* get_cached_username(uid_t uid);
+const char* get_cached_groupname(gid_t gid);
+void clear_username_cache(void);
+/* 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);
+#else
+#define die_if_bad_username(name) ((void)(name))
+#endif
+
+int execable_file(const char *name);
+char *find_execable(const char *filename);
+int exists_execable(const char *filename);
+
+/* 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[]);
+#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);
+pid_t xspawn(char **argv);
+
+int safe_waitpid(int pid, int *wstat, int options);
+/* 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(int pid);
+int wait_any_nohang(int *wstat);
+#define wait_crashed(w) ((w) & 127)
+#define wait_exitcode(w) ((w) >> 8)
+#define wait_stopsig(w) ((w) >> 8)
+#define wait_stopped(w) (((w) & 127) == 127)
+/* wait4pid(spawn(argv)) + NOFORK/NOEXEC (if configured) */
+int spawn_and_wait(char **argv);
+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);
+void restore_nofork_data(struct nofork_save_area *save);
+/* Does NOT check that applet is NOFORK, just blindly runs it */
+int run_nofork_applet(int applet_no, char **argv);
+int run_nofork_applet_prime(struct nofork_save_area *old, int applet_no, char **argv);
+
+/* 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()).
+ *
+ * forkexit_or_rexec(argv) = bare-bones "fork + parent exits" on MMU,
+ *      "vfork + re-exec ourself" on NOMMU. No fd redirection, no setsid().
+ *      Currently used for openvt and 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
+  void forkexit_or_rexec(void);
+  enum { re_execed = 0 };
+# define forkexit_or_rexec(argv)            forkexit_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) ATTRIBUTE_NORETURN;
+  void forkexit_or_rexec(char **argv);
+  extern bool re_execed;
+  int  BUG_fork_is_unavailable_on_nommu(void);
+  int  BUG_daemon_is_unavailable_on_nommu(void);
+  void BUG_bb_daemonize_is_unavailable_on_nommu(void);
+# 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);
+void bb_sanitize_stdio(void);
+/* Clear dangerous stuff, set PATH. Return 1 if was run by different user. */
+int sanitize_env_if_suid(void);
+
+
+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, ...);
+
+
+typedef struct llist_t {
+       char *data;
+       struct llist_t *link;
+} llist_t;
+void llist_add_to(llist_t **old_head, void *data);
+void llist_add_to_end(llist_t **list_head, void *data);
+void *llist_pop(llist_t **elm);
+void llist_unlink(llist_t **head, llist_t *elm);
+void llist_free(llist_t *elm, void (*freeit)(void *data));
+llist_t *llist_rev(llist_t *list);
+/* 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);
+#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) ATTRIBUTE_NORETURN;
+extern void bb_show_usage(void) ATTRIBUTE_NORETURN;
+extern void bb_error_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2)));
+extern void bb_error_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2)));
+extern void bb_perror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2)));
+extern void bb_simple_perror_msg(const char *s);
+extern void bb_perror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2)));
+extern void bb_simple_perror_msg_and_die(const char *s) __attribute__ ((noreturn));
+extern void bb_herror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2)));
+extern void bb_herror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2)));
+extern void bb_perror_nomsg_and_die(void) ATTRIBUTE_NORETURN;
+extern void bb_perror_nomsg(void);
+extern void bb_info_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2)));
+extern void bb_verror_msg(const char *s, va_list p, const char *strerr);
+
+/* 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);
+int echo_main(int argc, char** argv) MAIN_EXTERNALLY_VISIBLE;
+int test_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int kill_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if ENABLE_ROUTE
+void bb_displayroutes(int noresolve, int netstatfmt);
+#endif
+int chown_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if ENABLE_GUNZIP
+int gunzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#endif
+#if ENABLE_BUNZIP2
+int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#endif
+int bbunpack(char **argv,
+       char* (*make_new_name)(char *filename),
+       USE_DESKTOP(long long) int (*unpacker)(void)
+);
+
+
+/* Networking */
+int create_icmp_socket(void);
+int create_icmp6_socket(void);
+/* interface.c */
+/* This structure defines protocol families and their handlers. */
+struct aftype {
+       const char *name;
+       const char *title;
+       int af;
+       int alen;
+       char *(*print) (unsigned char *);
+       const char *(*sprint) (struct sockaddr *, int numeric);
+       int (*input) (/*int type,*/ const char *bufp, struct sockaddr *);
+       void (*herror) (char *text);
+       int (*rprint) (int options);
+       int (*rinput) (int typ, int ext, char **argv);
+
+       /* may modify src */
+       int (*getmask) (char *src, struct sockaddr * mask, char *name);
+};
+/* This structure defines hardware protocols and their handlers. */
+struct hwtype {
+       const char *name;
+       const char *title;
+       int type;
+       int alen;
+       char *(*print) (unsigned char *);
+       int (*input) (const char *, struct sockaddr *);
+       int (*activate) (int fd);
+       int suppress_null_addr;
+};
+extern smallint interface_opt_a;
+int display_interfaces(char *ifname);
+const struct aftype *get_aftype(const char *name);
+const struct hwtype *get_hwtype(const char *name);
+const struct hwtype *get_hwntype(int type);
+
+
+#ifndef BUILD_INDIVIDUAL
+extern int find_applet_by_name(const char *name);
+/* Returns only if applet is not found. */
+extern void run_applet_and_exit(const char *name, char **argv);
+extern void run_applet_no_and_exit(int a, char **argv) ATTRIBUTE_NORETURN;
+#endif
+
+#ifdef HAVE_MNTENT_H
+extern int match_fstype(const struct mntent *mt, const char *fstypes);
+extern struct mntent *find_mount_point(const char *name, const char *table);
+#endif
+extern void erase_mtab(const char * name);
+extern unsigned int tty_baud_to_value(speed_t speed);
+extern speed_t tty_value_to_baud(unsigned int value);
+extern void bb_warn_ignoring_args(int n);
+
+extern int get_linux_version_code(void);
+
+extern char *query_loop(const char *device);
+extern int del_loop(const char *device);
+/* 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);
+
+
+//TODO: pass buf pointer or return allocated buf (avoid statics)?
+char *bb_askpass(int timeout, const char * prompt);
+int bb_ask_confirmation(void);
+
+extern int bb_parse_mode(const char* s, mode_t* theMode);
+
+char *concat_path_file(const char *path, const char *filename);
+char *concat_subpath_file(const char *path, const char *filename);
+const char *bb_basename(const char *name);
+/* NB: can violate const-ness (similarly to strchr) */
+char *last_char_is(const char *s, int c);
+
+
+USE_DESKTOP(long long) int uncompress(int fd_in, int fd_out);
+int inflate(int in, int out);
+
+
+int bb_make_directory(char *path, long mode, int flags);
+
+int get_signum(const char *name);
+const char *get_signame(int number);
+void print_signames(void);
+
+char *bb_simplify_path(const char *path);
+
+#define FAIL_DELAY 3
+extern void bb_do_delay(int seconds);
+extern void change_identity(const struct passwd *pw);
+extern void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args) ATTRIBUTE_NORETURN;
+extern void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args);
+#if ENABLE_SELINUX
+extern void renew_current_security_context(void);
+extern void set_current_security_context(security_context_t sid);
+extern context_t set_security_context_component(security_context_t cur_context,
+                                               char *user, char *role, char *type, char *range);
+extern void setfscreatecon_or_die(security_context_t scontext);
+extern void selinux_preserve_fcontext(int fdesc);
+#else
+#define selinux_preserve_fcontext(fdesc) ((void)0)
+#endif
+extern void selinux_or_die(void);
+extern int restricted_shell(const char *shell);
+
+/* 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);
+extern int correct_password(const struct passwd *pw);
+/* Returns a ptr to static storage */
+extern char *pw_encrypt(const char *clear, const char *salt);
+extern int obscure(const char *old, const char *newval, const struct passwd *pwdp);
+
+int index_in_str_array(const char *const string_array[], const char *key);
+int index_in_strings(const char *strings, const char *key);
+int index_in_substr_array(const char *const string_array[], const char *key);
+int index_in_substrings(const char *strings, const char *key);
+const char *nth_string(const char *strings, int n);
+
+extern void print_login_issue(const char *issue_file, const char *tty);
+extern void print_login_prompt(void);
+
+/* 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);
+
+/* Returns number of lines changed, or -1 on error */
+extern int update_passwd(const char *filename, const char *username,
+                       const char *new_pw);
+
+/* NB: typically you want to pass fd 0, not 1. Think 'applet | grep something' */
+int get_terminal_width_height(int fd, int *width, int *height);
+
+int ioctl_or_perror(int fd, unsigned request, void *argp, const char *fmt,...) __attribute__ ((format (printf, 4, 5)));
+void ioctl_or_perror_and_die(int fd, unsigned request, void *argp, const char *fmt,...) __attribute__ ((format (printf, 4, 5)));
+#if ENABLE_IOCTL_HEX2STR_ERROR
+int bb_ioctl_or_warn(int fd, unsigned request, void *argp, const char *ioctl_name);
+void bb_xioctl(int fd, unsigned request, void *argp, const char *ioctl_name);
+#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);
+void bb_xioctl(int fd, unsigned request, void *argp);
+#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);
+void add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name);
+void reset_ino_dev_hashtable(void);
+#ifdef __GLIBC__
+/* At least glibc has horrendously large inline for this, so wrap it */
+unsigned long long bb_makedev(unsigned int major, unsigned int minor);
+#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;
+       USE_FEATURE_EDITING_SAVEHISTORY(const char *hist_file;)
+       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);
+/* 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);
+#else
+int read_line_input(const char* prompt, char* command, int maxsize);
+#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) */
+       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 */
+} 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,
+       PSSCAN_ARGVN    = (1 << 16) * (ENABLE_PGREP | ENABLE_PKILL),
+       USE_SELINUX(PSSCAN_CONTEXT = 1 << 17,)
+       PSSCAN_START_TIME = 1 << 18,
+       /* 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,
+};
+//procps_status_t* alloc_procps_scan(void);
+void free_procps_scan(procps_status_t* sp);
+procps_status_t* procps_scan(procps_status_t* sp, int flags);
+/* 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);
+pid_t *find_pid_by_name(const char* procName);
+pid_t *pidlist_reverse(pid_t *pidList);
+
+
+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);
+
+typedef struct sha1_ctx_t {
+       uint32_t count[2];
+       uint32_t hash[5];
+       uint32_t wbuf[16];
+} sha1_ctx_t;
+void sha1_begin(sha1_ctx_t *ctx);
+void sha1_hash(const void *data, size_t length, sha1_ctx_t *ctx);
+void *sha1_end(void *resbuf, sha1_ctx_t *ctx);
+
+typedef struct md5_ctx_t {
+       uint32_t A;
+       uint32_t B;
+       uint32_t C;
+       uint32_t D;
+       uint64_t total;
+       uint32_t buflen;
+       char buffer[128];
+} md5_ctx_t;
+void md5_begin(md5_ctx_t *ctx);
+void md5_hash(const void *data, size_t length, md5_ctx_t *ctx);
+void *md5_end(void *resbuf, md5_ctx_t *ctx);
+
+uint32_t *crc32_filltable(uint32_t *tbl256, int endian);
+
+
+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,
+#if ENABLE_SELINUX
+       FILEUTILS_PRESERVE_SECURITY_CONTEXT = 0x80,
+       FILEUTILS_SET_SECURITY_CONTEXT = 0x100
+#endif
+};
+
+#define FILEUTILS_CP_OPTSTR "pdRfils" USE_SELINUX("c")
+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)
+
+
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#endif /* __LIBBUSYBOX_H__ */
diff --git a/include/platform.h b/include/platform.h
new file mode 100644 (file)
index 0000000..5193485
--- /dev/null
@@ -0,0 +1,338 @@
+/* vi: set sw=4 ts=4: */
+/*
+   Copyright 2006, Bernhard Fischer
+
+   Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+*/
+#ifndef        __PLATFORM_H
+#define __PLATFORM_H   1
+
+/* Convenience macros to test the version of gcc. */
+#undef __GNUC_PREREQ
+#if defined __GNUC__ && defined __GNUC_MINOR__
+# define __GNUC_PREREQ(maj, min) \
+               ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else
+# define __GNUC_PREREQ(maj, min) 0
+#endif
+
+/* __restrict is known in EGCS 1.2 and above. */
+#if !__GNUC_PREREQ (2,92)
+# ifndef __restrict
+#  define __restrict     /* Ignore */
+# endif
+#endif
+
+/* Define macros for some gcc attributes.  This permits us to use the
+   macros freely, and know that they will come into play for the
+   version of gcc in which they are supported.  */
+
+#if !__GNUC_PREREQ (2,7)
+# ifndef __attribute__
+#  define __attribute__(x)
+# endif
+#endif
+
+#undef inline
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ > 199901L
+/* it's a keyword */
+#else
+# if __GNUC_PREREQ (2,7)
+#  define inline __inline__
+# else
+#  define inline
+# endif
+#endif
+
+#ifndef __const
+# define __const const
+#endif
+
+# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__))
+# define ATTRIBUTE_PACKED __attribute__ ((__packed__))
+# define ATTRIBUTE_ALIGNED(m) __attribute__ ((__aligned__(m)))
+/* __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 ATTRIBUTE_DEPRECATED __attribute__ ((__deprecated__))
+#   define ATTRIBUTE_UNUSED_RESULT __attribute__ ((warn_unused_result))
+#  else
+#   define ATTRIBUTE_DEPRECATED /* n/a */
+#   define ATTRIBUTE_UNUSED_RESULT /* n/a */
+#  endif
+# else
+#  define ALWAYS_INLINE inline /* n/a */
+#  define NOINLINE /* n/a */
+#  define ATTRIBUTE_DEPRECATED /* n/a */
+#  define ATTRIBUTE_UNUSED_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 /* GNUC >= 4.1 */
+
+/* We use __extension__ in some places to suppress -pedantic warnings
+   about GCC extensions.  This feature didn't work properly before
+   gcc 2.8.  */
+#if !__GNUC_PREREQ (2,8)
+# ifndef __extension__
+#  define __extension__
+# endif
+#endif
+
+/* gcc-2.95 had no va_copy but only __va_copy. */
+#if !__GNUC_PREREQ (3,0)
+# include <stdarg.h>
+# if !defined va_copy && defined __va_copy
+#  define va_copy(d,s) __va_copy((d),(s))
+# endif
+#endif
+
+/* ---- Endian Detection ------------------------------------ */
+
+#if (defined __digital__ && defined __unix__)
+# include <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
+
+#if BB_BIG_ENDIAN
+#define SWAP_BE16(x) (x)
+#define SWAP_BE32(x) (x)
+#define SWAP_BE64(x) (x)
+#define SWAP_LE16(x) bswap_16(x)
+#define SWAP_LE32(x) bswap_32(x)
+#define SWAP_LE64(x) bswap_64(x)
+#else
+#define SWAP_BE16(x) bswap_16(x)
+#define SWAP_BE32(x) bswap_32(x)
+#define SWAP_BE64(x) bswap_64(x)
+#define SWAP_LE16(x) (x)
+#define SWAP_LE32(x) (x)
+#define SWAP_LE64(x) (x)
+#endif
+
+/* ---- Networking ------------------------------------------ */
+#ifndef __APPLE__
+# include <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 __signed__ 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 BB_NOMMU 1
+#define USE_FOR_NOMMU(...) __VA_ARGS__
+#define USE_FOR_MMU(...)
+#else
+#define BB_MMU 1
+/* BB_NOMMU is not defined in this case! */
+#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
+#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
+
+
+#if !defined(BLKSSZGET)
+#define BLKSSZGET _IO(0x12, 104)
+#endif
+#if !defined(BLKGETSIZE64)
+#define BLKGETSIZE64 _IOR(0x12,114,size_t)
+#endif
+#endif
+
+#endif /* platform.h   */
diff --git a/include/pwd_.h b/include/pwd_.h
new file mode 100644 (file)
index 0000000..f47e4eb
--- /dev/null
@@ -0,0 +1,122 @@
+/* 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>
+ */
+
+#if !ENABLE_USE_BB_PWD_GRP
+
+#include <pwd.h>
+
+#else
+
+#ifndef        _PWD_H
+#define        _PWD_H 1
+
+/* The passwd structure.  */
+struct passwd {
+       char *pw_name;          /* Username.  */
+       char *pw_passwd;        /* Password.  */
+       uid_t pw_uid;           /* User ID.  */
+       gid_t pw_gid;           /* Group ID.  */
+       char *pw_gecos;         /* Real name.  */
+       char *pw_dir;           /* Home directory.  */
+       char *pw_shell;         /* Shell program.  */
+};
+
+
+#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.
+ * In theory it isn't necessary, but I saw weird interactions at link time.
+ * Let's play safe */
+
+
+/* Rewind the password-file stream.  */
+extern void setpwent(void);
+
+/* Close the password-file stream.  */
+extern void endpwent(void);
+
+/* Read an entry from the password-file stream, opening it if necessary.  */
+extern struct passwd *getpwent(void);
+
+/* Read an entry from STREAM.  */
+extern struct passwd *fgetpwent(FILE *__stream);
+
+/* Write the given entry onto the given stream.  */
+extern int putpwent(__const struct passwd *__restrict __p,
+                    FILE *__restrict __f);
+
+/* Search for an entry with a matching user ID.  */
+extern struct passwd *getpwuid(uid_t __uid);
+
+/* Search for an entry with a matching username.  */
+extern struct passwd *getpwnam(__const char *__name);
+
+/* Reentrant versions of some of the functions above.
+
+   PLEASE NOTE: the `getpwent_r' function is not (yet) standardized.
+   The interface may change in later versions of this library.  But
+   the interface is designed following the principals used for the
+   other reentrant functions so the chances are good this is what the
+   POSIX people would choose.  */
+
+extern int getpwent_r(struct passwd *__restrict __resultbuf,
+                      char *__restrict __buffer, size_t __buflen,
+                      struct passwd **__restrict __result);
+
+extern int getpwuid_r(uid_t __uid,
+                      struct passwd *__restrict __resultbuf,
+                      char *__restrict __buffer, size_t __buflen,
+                      struct passwd **__restrict __result);
+
+extern int getpwnam_r(__const char *__restrict __name,
+                      struct passwd *__restrict __resultbuf,
+                      char *__restrict __buffer, size_t __buflen,
+                      struct passwd **__restrict __result);
+
+/* Read an entry from STREAM.  This function is not standardized and
+   probably never will.  */
+extern int fgetpwent_r(FILE *__restrict __stream,
+                       struct passwd *__restrict __resultbuf,
+                       char *__restrict __buffer, size_t __buflen,
+                       struct passwd **__restrict __result);
+
+/* Re-construct the password-file line for the given uid
+   in the given buffer.  This knows the format that the caller
+   will expect, but this need not be the format of the password file.  */
+extern int getpw(uid_t __uid, char *__buffer);
+
+#endif /* pwd.h  */
+#endif
diff --git a/include/rtc_.h b/include/rtc_.h
new file mode 100644 (file)
index 0000000..df359da
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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_
+
+#include "libbb.h"
+
+extern int rtc_adjtime_is_utc(void);
+extern int rtc_xopen(const char **default_rtc, int flags);
+extern time_t rtc_read_time(int fd, int utc);
+
+
+
+/*
+ * 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
+
+#endif
diff --git a/include/shadow_.h b/include/shadow_.h
new file mode 100644 (file)
index 0000000..92bcde8
--- /dev/null
@@ -0,0 +1,114 @@
+/* vi: set sw=4 ts=4: */
+/* Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+/* Declaration of types and functions for shadow password suite */
+
+#if !ENABLE_USE_BB_SHADOW
+
+#include <shadow.h>
+
+#else
+
+#ifndef _SHADOW_H
+#define _SHADOW_H 1
+
+/* Paths to the user database files */
+#ifndef _PATH_SHADOW
+#define _PATH_SHADOW "/etc/shadow"
+#endif
+
+/* Structure of the password file */
+struct spwd {
+       char *sp_namp;          /* Login name */
+       char *sp_pwdp;          /* Encrypted password */
+       long sp_lstchg;         /* Date of last change */
+       long sp_min;            /* Minimum number of days between changes */
+       long sp_max;            /* Maximum number of days between changes */
+       long sp_warn;           /* Number of days to warn user to change the password */
+       long sp_inact;          /* Number of days the account may be inactive */
+       long sp_expire;         /* Number of days since 1970-01-01 until account expires */
+       unsigned long sp_flag;  /* Reserved */
+};
+
+
+#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.
+ * In theory it isn't necessary, but I saw weird interactions at link time.
+ * Let's play safe */
+
+
+/* Open database for reading */
+extern void setspent(void);
+
+/* Close database */
+extern void endspent(void);
+
+/* Get next entry from database, perhaps after opening the file */
+extern struct spwd *getspent(void);
+
+/* Get shadow entry matching NAME */
+extern struct spwd *getspnam(__const char *__name);
+
+/* Read shadow entry from STRING */
+extern struct spwd *sgetspent(__const char *__string);
+
+/* Read next shadow entry from STREAM */
+extern struct spwd *fgetspent(FILE *__stream);
+
+/* Write line containing shadow password entry to stream */
+extern int putspent(__const struct spwd *__p, FILE *__stream);
+
+/* Reentrant versions of some of the functions above */
+extern int getspent_r(struct spwd *__result_buf, char *__buffer,
+                      size_t __buflen, struct spwd **__result);
+
+extern int getspnam_r(__const char *__name, struct spwd *__result_buf,
+                      char *__buffer, size_t __buflen,
+                      struct spwd **__result);
+
+extern int sgetspent_r(__const char *__string, struct spwd *__result_buf,
+                       char *__buffer, size_t __buflen,
+                       struct spwd **__result);
+
+extern int fgetspent_r(FILE *__stream, struct spwd *__result_buf,
+                       char *__buffer, size_t __buflen,
+                       struct spwd **__result);
+/* Protect password file against multi writers */
+extern int lckpwdf(void);
+
+/* Unlock password file */
+extern int ulckpwdf(void);
+
+#endif /* shadow.h */
+#endif
diff --git a/include/unarchive.h b/include/unarchive.h
new file mode 100644 (file)
index 0000000..bfd6488
--- /dev/null
@@ -0,0 +1,129 @@
+/* vi: set sw=4 ts=4: */
+#ifndef        __UNARCHIVE_H__
+#define        __UNARCHIVE_H__
+
+#define ARCHIVE_PRESERVE_DATE           1
+#define ARCHIVE_CREATE_LEADING_DIRS     2
+#define ARCHIVE_EXTRACT_UNCONDITIONAL   4
+#define ARCHIVE_EXTRACT_QUIET           8
+#define ARCHIVE_EXTRACT_NEWER           16
+#define ARCHIVE_NOPRESERVE_OWN          32
+#define ARCHIVE_NOPRESERVE_PERM         64
+
+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 (*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 (*action_header)(const file_header_t *);
+
+       /* process the data component, e.g. extract to filesystem */
+       void (*action_data)(struct archive_handle_t *);
+
+       /* How to process any sub archive, e.g. get_header_tar_gz */
+       char (*action_data_subarchive)(struct archive_handle_t *);
+
+       /* Contains the handle to a sub archive */
+       struct archive_handle_t *sub_archive;
+
+       /* The raw stream as read from disk or stdin */
+       int src_fd;
+
+       /* Count the number of bytes processed */
+       off_t offset;
+
+       /* Function that skips data: read_by_char or read_by_skip */
+       void (*seek)(const struct archive_handle_t *archive_handle, const unsigned amount);
+
+       /* Temporary storage */
+       char *buffer;
+
+       /* Flags and misc. stuff */
+       unsigned char flags;
+
+} archive_handle_t;
+
+
+extern archive_handle_t *init_handle(void);
+
+extern char filter_accept_all(archive_handle_t *archive_handle);
+extern char filter_accept_list(archive_handle_t *archive_handle);
+extern char filter_accept_list_reassign(archive_handle_t *archive_handle);
+extern char filter_accept_reject_list(archive_handle_t *archive_handle);
+
+extern void unpack_ar_archive(archive_handle_t *ar_archive);
+
+extern void data_skip(archive_handle_t *archive_handle);
+extern void data_extract_all(archive_handle_t *archive_handle);
+extern void data_extract_to_stdout(archive_handle_t *archive_handle);
+extern void data_extract_to_buffer(archive_handle_t *archive_handle);
+
+extern void header_skip(const file_header_t *file_header);
+extern void header_list(const file_header_t *file_header);
+extern void header_verbose_list(const file_header_t *file_header);
+
+extern char get_header_ar(archive_handle_t *archive_handle);
+extern char get_header_cpio(archive_handle_t *archive_handle);
+extern char get_header_tar(archive_handle_t *archive_handle);
+extern char get_header_tar_bz2(archive_handle_t *archive_handle);
+extern char get_header_tar_lzma(archive_handle_t *archive_handle);
+extern char get_header_tar_gz(archive_handle_t *archive_handle);
+
+extern void seek_by_jump(const archive_handle_t *archive_handle, unsigned amount);
+extern void seek_by_read(const archive_handle_t *archive_handle, unsigned amount);
+
+extern ssize_t archive_xread_all_eof(archive_handle_t *archive_handle, unsigned char *buf, size_t count);
+
+extern void data_align(archive_handle_t *archive_handle, unsigned boundary);
+extern const llist_t *find_list_entry(const llist_t *list, const char *filename);
+extern const llist_t *find_list_entry2(const llist_t *list, const char *filename);
+
+/* 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);
+int read_bunzip(bunzip_data *bd, char *outbuf, int len);
+void dealloc_bunzip(bunzip_data *bd);
+
+typedef struct inflate_unzip_result {
+       off_t bytes_out;
+       uint32_t crc;
+} inflate_unzip_result;
+
+extern USE_DESKTOP(long long) int unpack_bz2_stream(int src_fd, int dst_fd);
+extern USE_DESKTOP(long long) int inflate_unzip(inflate_unzip_result *res, off_t compr_size, int src_fd, int dst_fd);
+extern USE_DESKTOP(long long) int unpack_gz_stream(int src_fd, int dst_fd);
+extern USE_DESKTOP(long long) int unpack_lzma_stream(int src_fd, int dst_fd);
+
+#if BB_MMU
+extern int open_transformer(int src_fd,
+       USE_DESKTOP(long long) int (*transformer)(int src_fd, int dst_fd));
+#define open_transformer(src_fd, transformer, transform_prog) open_transformer(src_fd, transformer)
+#else
+extern int open_transformer(int src_fd, const char *transform_prog);
+#define open_transformer(src_fd, transformer, transform_prog) open_transformer(src_fd, transform_prog)
+#endif
+
+#endif
diff --git a/include/usage.h b/include/usage.h
new file mode 100644 (file)
index 0000000..97f4318
--- /dev/null
@@ -0,0 +1,4455 @@
+/* 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__
+
+#define addgroup_trivial_usage \
+       "[-g GID] " USE_FEATURE_ADDUSER_TO_GROUP("[user_name] ") "group_name"
+#define addgroup_full_usage \
+       "Add a group " USE_FEATURE_ADDUSER_TO_GROUP("or add an user to a group") "\n" \
+     "\nOptions:" \
+     "\n       -g GID  Group id"
+
+#define adduser_trivial_usage \
+       "[OPTIONS] user_name"
+#define adduser_full_usage \
+       "Add an 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 \
+       "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 \
+       "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\n"
+#define arp_full_usage \
+       "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 \
+       "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 ash_trivial_usage \
+       "[FILE]...\n" \
+       "or: ash -c command [args]..."
+#define ash_full_usage \
+       "The ash shell"
+
+#define awk_trivial_usage \
+       "[OPTION]... [program-text] [FILE...]"
+#define awk_full_usage \
+       "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 \
+       "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 brctl_trivial_usage \
+       "COMMAND [BRIDGE [INTERFACE]]"
+#define brctl_full_usage \
+       "Manage ethernet bridges.\n" \
+     "\nCommands:" \
+     "\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 \
+       "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 \
+       "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 \
+       "Uncompress to stdout"
+
+#define unlzma_trivial_usage \
+       "[OPTION]... [FILE]"
+#define unlzma_full_usage \
+       "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 \
+       "Uncompress to stdout"
+
+#define cal_trivial_usage \
+       "[-jy] [[month] year]"
+#define cal_full_usage \
+       "Display a calendar\n" \
+     "\nOptions:" \
+     "\n       -j      Use julian dates" \
+     "\n       -y      Display the entire year" \
+
+#define cat_trivial_usage \
+       "[-u] [FILE]..."
+#define cat_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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[:group]] [-U user[:group]] [-e dir] " \
+       "[-/ dir] [-n nice] [-m bytes] [-d bytes] [-o files] " \
+       "[-p processes] [-f bytes] [-c bytes] prog args"
+#define chpst_full_usage \
+       "Change the process state and run specified program\n" \
+     "\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 INC          Add INC to nice value" \
+     "\n       -m BYTES        Limit data segment, stack segment, locked physical pages," \
+     "\n                       and total of all segment per process to BYTES each" \
+     "\n       -d BYTES        Limit data segment" \
+     "\n       -o N            Limit the number of open file descriptors per process to N" \
+     "\n       -p N            Limit number of processes per uid to N" \
+     "\n       -f BYTES        Limit output file size to BYTES" \
+     "\n       -c BYTES        Limit core file size to BYTES" \
+     "\n       -v              Verbose" \
+     "\n       -P              Run prog in a new process group" \
+     "\n       -0              Close standard input" \
+     "\n       -1              Close standard output" \
+     "\n       -2              Close standard error" \
+
+#define setuidgid_trivial_usage \
+       "account prog args"
+#define setuidgid_full_usage \
+       "Set uid and gid to account's uid and gid, removing all supplementary\n" \
+       "groups, then run prog"
+#define envuidgid_trivial_usage \
+       "account prog args"
+#define envuidgid_full_usage \
+       "Set $UID to account's uid and $GID to account's gid, then run prog"
+#define envdir_trivial_usage \
+       "dir prog args"
+#define envdir_full_usage \
+       "Set various environment variables as specified by files\n" \
+       "in the directory dir, then run prog"
+#define softlimit_trivial_usage \
+       "[-a allbytes] [-c corebytes] [-d databytes] [-f filebytes] " \
+       "[-l lockbytes] [-m membytes] [-o openfiles] [-p processes] " \
+       "[-r residentbytes] [-s stackbytes] [-t cpusecs] prog args"
+#define softlimit_full_usage \
+       "Set soft resource limits, then run prog\n" \
+     "\nOptions:" \
+     "\n       -m n    Same as -d n -s n -l n -a n" \
+     "\n       -d n    Limit the data segment per process to n bytes" \
+     "\n       -s n    Limit the stack segment per process to n bytes" \
+     "\n       -l n    Limit the locked physical pages per process to n bytes" \
+     "\n       -a n    Limit the total of all segments per process to n bytes" \
+     "\n       -o n    Limit the number of open file descriptors per process to n" \
+     "\n       -p n    Limit the number of processes per uid to n" \
+     "\nOptions controlling file sizes:" \
+     "\n       -f n    Limit output file sizes to n bytes" \
+     "\n       -c n    Limit core file sizes to n bytes" \
+     "\nEfficiency opts:" \
+     "\n       -r n    Limit the resident set size to n bytes. This limit is not" \
+     "\n               enforced unless physical memory is full" \
+     "\n       -t n    Limit the CPU time to n seconds. This limit is not enforced" \
+     "\n               except that the process receives a SIGXCPU signal after n seconds" \
+     "\n" \
+     "\nSome options may have no effect on some operating systems" \
+     "\nn may be =, indicating that soft limit should be set equal to hard limit" \
+
+#define chroot_trivial_usage \
+       "NEWROOT [COMMAND...]"
+#define chroot_full_usage \
+       "Run COMMAND with root directory set to NEWROOT"
+#define chroot_example_usage \
+       "$ ls -l /bin/ls\n" \
+       "lrwxrwxrwx    1 root     root          12 Apr 13 00:46 /bin/ls -> /BusyBox\n" \
+       "# mount /dev/hdc1 /mnt -t minix\n" \
+       "# chroot /mnt\n" \
+       "# ls -l /bin/ls\n" \
+       "-rwxr-xr-x    1 root     root        40816 Feb  5 07:45 /bin/ls*\n"
+
+#define chvt_trivial_usage \
+       "N"
+#define chvt_full_usage \
+       "Change the foreground virtual terminal to /dev/ttyN"
+
+#define cksum_trivial_usage \
+       "FILES..."
+#define cksum_full_usage \
+       "Calculate the CRC32 checksums of FILES"
+
+#define clear_trivial_usage \
+       ""
+#define clear_full_usage \
+       "Clear screen"
+
+#define cmp_trivial_usage \
+       "[-l] [-s] FILE1 [FILE2" USE_DESKTOP(" [SKIP1 [SKIP2]") "]]"
+#define cmp_full_usage \
+       "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 \
+       "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 \
+       "Print the config file which built busybox"
+
+#define bbsh_trivial_usage \
+       "[FILE]...\n" \
+       "or: bbsh -c command [args]..."
+#define bbsh_full_usage \
+       "The bbsh shell (command interpreter)"
+
+#define chrt_trivial_usage \
+       "[OPTION]... [prio] [pid | command [arg]...]"
+#define chrt_full_usage \
+       "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 \
+       "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 \
+       "-[dimtuv][F cpiofile]"
+#define cpio_full_usage \
+       "Extract or list files from a cpio archive\n" \
+       "Main operation mode:" \
+     "\n       d       Make leading directories" \
+     "\n       i       Extract" \
+     "\n       m       Preserve mtime" \
+     "\n       t       List" \
+     "\n       v       Verbose" \
+     "\n       u       Unconditional overwrite" \
+     "\n       F       Input from file" \
+
+#define crond_trivial_usage \
+       "-fbS -l N " USE_DEBUG_CROND_OPTION("-d N ") "-L LOGFILE -c DIR"
+#define crond_full_usage \
+       "       -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_DEBUG_CROND_OPTION( \
+     "\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 \
+       "       -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 \
+       "[-a des|md5] [string]"
+#define cryptpw_full_usage \
+       "Output crypted string.\n" \
+       "If string isn't supplied on cmdline, read it from stdin.\n" \
+     "\nOptions:" \
+     "\n       -a      Algorithm to use (default: md5)" \
+
+#define cut_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define cut_full_usage \
+       "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]... [MMDDhhmm[[CC]YY][.ss]] [+FORMAT]"
+#define date_full_usage \
+       "Display current time in the given FORMAT, or set system date\n" \
+     "\nOptions:" \
+     "\n       -R              Output RFC-822 compliant date string" \
+     "\n       -d STRING       Display time described by STRING, not 'now'" \
+       USE_FEATURE_DATE_ISOFMT( \
+     "\n       -I[TIMESPEC]    Output an ISO-8601 compliant date/time string" \
+     "\n                       TIMESPEC='date' (or missing) for date only," \
+     "\n                       'hours', 'minutes', or 'seconds' for date and" \
+     "\n                       time to the indicated precision" \
+     "\n       -D hint         Use 'hint' as date format, via strptime()" \
+       ) \
+     "\n       -s STRING       Set time described by STRING" \
+     "\n       -r FILE         Display the last modification time of FILE" \
+     "\n       -u              Print or sets Coordinated Universal Time" \
+
+#define date_example_usage \
+       "$ date\n" \
+       "Wed Apr 12 18:52:41 MDT 2000\n"
+
+#define dc_trivial_usage \
+       "expression..."
+#define dc_full_usage \
+       "This is a Tiny RPN calculator that understands the following operations:\n" \
+       "+, add, -, sub, *, mul, /, div, %, mod, **, exp, and, or, not, eor.\n" \
+       "For example: 'dc 2 2 add' -> 4, and 'dc 8 8 \\* 2 2 + /' -> 16.\n" \
+     "\nOptions:" \
+     "\np - Print the value on the top of the stack, without altering the stack" \
+     "\nf - Print the entire contents of the stack without altering anything" \
+     "\no - Pop the value off the top of the stack and use it to set the output radix" \
+     "\n    Only 10 and 16 are supported" \
+
+#define dc_example_usage \
+       "$ dc 2 2 + p\n" \
+       "4\n" \
+       "$ dc 8 8 \\* 2 2 + / p\n" \
+       "16\n" \
+       "$ dc 0 1 and p\n" \
+       "0\n" \
+       "$ dc 0 1 or p\n" \
+       "1\n" \
+       "$ echo 72 9 div 8 mul p | dc\n" \
+       "64\n"
+
+#define dd_trivial_usage \
+       "[if=FILE] [of=FILE] " USE_FEATURE_DD_IBS_OBS("[ibs=N] [obs=N] ") "[bs=N] [count=N] [skip=N]\n" \
+       "       [seek=N]" USE_FEATURE_DD_IBS_OBS(" [conv=notrunc|noerror|sync]")
+#define dd_full_usage \
+       "Copy a file 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" \
+     "\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 \
+       "Deallocate unused virtual terminal /dev/ttyN"
+
+#define delgroup_trivial_usage \
+       USE_FEATURE_DEL_USER_FROM_GROUP("[USER] ")"GROUP"
+#define delgroup_full_usage \
+       "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 \
+       "Delete user USER from the system"
+
+#define devfsd_trivial_usage \
+       "mntpnt [-v]" USE_DEVFSD_FG_NP("[-fg][-np]")
+#define devfsd_full_usage \
+       "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" \
+       )
+
+/* -k is accepted but ignored for !HUMAN_READABLE,
+ * but we won't mention this (unimportant) */
+#if ENABLE_FEATURE_HUMAN_READABLE || ENABLE_FEATURE_DF_INODE
+#define DF_HAS_OPTIONS(x) x
+#else
+#define DF_HAS_OPTIONS(x)
+#endif
+#define df_trivial_usage \
+       DF_HAS_OPTIONS("[-") \
+       USE_FEATURE_HUMAN_READABLE("hmk") USE_FEATURE_DF_INODE("i") \
+       DF_HAS_OPTIONS("] ") "[FILESYSTEM...]"
+#define df_full_usage \
+       "Print filesystem usage statistics\n" \
+       DF_HAS_OPTIONS("\nOptions:") \
+       USE_FEATURE_HUMAN_READABLE( \
+     "\n       -h      Human readable (e.g. 1K 243M 2G)" \
+     "\n       -m      1024*1024 blocks" \
+     "\n       -k      1024 blocks" \
+       ) \
+       USE_FEATURE_DF_INODE( \
+     "\n       -i      Inodes" \
+       )
+#define df_example_usage \
+       "$ df\n" \
+       "Filesystem           1k-blocks      Used Available Use% Mounted on\n" \
+       "/dev/sda3              8690864   8553540    137324  98% /\n" \
+       "/dev/sda1                64216     36364     27852  57% /boot\n" \
+       "$ df /dev/sda3\n" \
+       "Filesystem           1k-blocks      Used Available Use% Mounted on\n" \
+       "/dev/sda3              8690864   8553540    137324  98% /\n"
+
+#define dhcprelay_trivial_usage \
+       "[client_device_list] [server_device]"
+#define dhcprelay_full_usage \
+       "Relay dhcp requests from client devices to server device"
+
+#define dhcprelay_trivial_usage \
+       "[client_device_list] [server_device]"
+#define dhcprelay_full_usage \
+       "Relay dhcp requests from client devices to server device"
+
+#define diff_trivial_usage \
+       "[-abdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2"
+#define diff_full_usage \
+       "Compare files line by line and output the differences between them.\n" \
+       "This 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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "Print out a binary keyboard translation table to standard output"
+#define dumpkmap_example_usage \
+       "$ dumpkmap > keymap\n"
+
+#define dumpleases_trivial_usage \
+       "[-r|-a] [-f LEASEFILE]"
+#define dumpleases_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "Return an exit code of FALSE (1)"
+
+#define false_example_usage \
+       "$ false\n" \
+       "$ echo $?\n" \
+       "1\n"
+
+#define fbset_trivial_usage \
+       "[options] [mode]"
+#define fbset_full_usage \
+       "Show and modify frame buffer settings"
+
+#define fbset_example_usage \
+       "$ fbset\n" \
+       "mode \"1024x768-76\"\n" \
+       "       # D: 78.653 MHz, H: 59.949 kHz, V: 75.694 Hz\n" \
+       "       geometry 1024 768 1024 768 16\n" \
+       "       timings 12714 128 32 16 4 128 4\n" \
+       "       accel false\n" \
+       "       rgba 5/11,6/5,5/0,0/0\n" \
+       "endmode\n"
+
+#define fdflush_trivial_usage \
+       "DEVICE"
+#define fdflush_full_usage \
+       "Force floppy disk drive to detect disk change"
+
+#define fdformat_trivial_usage \
+       "[-n] DEVICE"
+#define fdformat_full_usage \
+       "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 \
+       "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 fetchmail_trivial_usage \
+       "[-w timeout] [-U user] -P password [-X] [-t] [-z] server[:port] maildir [prog]"
+#define fetchmail_full_usage \
+       "Fetch content of remote mailbox to local Maildir.\n" \
+     "\nOptions:" \
+     "\n       -w timeout      Set timeout on network operations" \
+     "\n       -U username     Authenticate with specified username/password" \
+     "\n       -P password" \
+     "\n       -X              Use openssl connection helper for secured servers" \
+     "\n       -t              Get only headers" \
+     "\n       -z              Delete messages on server" \
+     "\n       prog            Run prog <message_file> on message delivery" \
+
+#define findfs_trivial_usage \
+       "LABEL=label or UUID=uuid"
+#define findfs_full_usage \
+       "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 \
+       "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       -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 fold_trivial_usage \
+       "[-bs] [-w WIDTH] [FILE]"
+#define fold_full_usage \
+       "Wrap input lines in each FILE (standard input by default), writing to\n" \
+       "standard output\n" \
+     "\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 \
+       "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 \
+       "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 \
+       "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 \
+       "Perform a consistency check for MINIX filesystems\n" \
+     "\nOptions:" \
+     "\n       -l      List all filenames" \
+     "\n       -r      Perform interactive repairs" \
+     "\n       -a      Perform automatic repairs" \
+     "\n       -v      Verbose" \
+     "\n       -s      Output super-block information" \
+     "\n       -m      Activate MINIX-like \"mode not cleared\" warnings" \
+     "\n       -f      Force file system check" \
+
+#define ftpget_trivial_usage \
+       "[options] remote-host local-file remote-file"
+#define ftpget_full_usage \
+       "Retrieve a remote file via FTP\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -c,--continue   Continue previous transfer" \
+     "\n       -v,--verbose    Verbose" \
+     "\n       -u,--username   Username" \
+     "\n       -p,--password   Password" \
+     "\n       -P,--port       Port number" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\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 \
+       "Store a local file on a remote machine via FTP\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -v,--verbose    Verbose" \
+     "\n       -u,--username   Username" \
+     "\n       -p,--password   Password" \
+     "\n       -P,--port       Port number" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\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 \
+       "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
+#define getenforce_full_usage
+
+#define getopt_trivial_usage \
+       "[OPTIONS]..."
+#define getopt_full_usage \
+       "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 \
+       "       -a      Show all SELinux booleans"
+
+#define getty_trivial_usage \
+       "[OPTIONS] BAUD_RATE TTY [TERMTYPE]"
+#define getty_full_usage \
+       "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") \
+       "] PATTERN [FILEs...]"
+#define grep_full_usage \
+       "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") \
+
+#define grep_example_usage \
+       "$ grep root /etc/passwd\n" \
+       "root:x:0:0:root:/root:/bin/bash\n" \
+       "$ grep ^[rR]oo. /etc/passwd\n" \
+       "root:x:0:0:root:/root:/bin/bash\n"
+
+#define gunzip_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define gunzip_full_usage \
+       "Uncompress 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 \
+       "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]"
+#define halt_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "hd is an alias for hexdump -C"
+
+#define hostid_trivial_usage \
+       ""
+#define hostid_full_usage \
+       "Print out a unique 32-bit identifier for the machine"
+
+#define hostname_trivial_usage \
+       "[OPTION] [hostname | -F FILE]"
+#define hostname_full_usage \
+       "Get or set 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 \
+       "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_GETOPT_LONG( \
+       "[-r|--show] [-s|--hctosys] [-w|--systohc]" \
+       " [-l|--localtime] [-u|--utc]" \
+       " [-f FILE]" \
+       ) \
+       SKIP_GETOPT_LONG( \
+       "[-r] [-s] [-w] [-l] [-u] [-f FILE]" \
+       )
+#define hwclock_full_usage \
+       "Query and set hardware clock (RTC)\n" \
+     "\nOptions:" \
+     "\n       -r      Show time from hardware clock" \
+     "\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 \
+       "Print information about USER or the current user\n" \
+     "\nOptions:" \
+       USE_SELINUX( \
+     "\n       -Z      Print the security context" \
+       ) \
+     "\n       -g      Print group ID" \
+     "\n       -u      Print user ID" \
+     "\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 \
+       "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 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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 insmod_trivial_usage \
+       USE_FEATURE_2_4_MODULES("[OPTION]... ") "MODULE [symbol=value]..."
+#define insmod_full_usage \
+       "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" \
+       )
+
+#define install_trivial_usage \
+       "[-cgmops] [sources] dest|directory"
+#define install_full_usage \
+       "Copy files and set attributes\n" \
+     "\nOptions:" \
+     "\n       -c      Copy the file, default" \
+     "\n       -d      Create directories" \
+     "\n       -g      Set group ownership" \
+     "\n       -m      Set permissions" \
+     "\n       -o      Set ownership" \
+     "\n       -p      Preserve date" \
+     "\n       -s      Strip symbol tables" \
+       USE_SELINUX( \
+     "\n       -Z      Set security context of copy" \
+       )
+
+/* 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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "       -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 \
+       "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 \
+       "iproute { list | flush } SELECTOR\n" \
+       "iproute get ADDRESS [from ADDRESS iif STRING]\n" \
+       "                       [oif STRING]  [tos TOS]\n" \
+       "iproute { add | del | change | append | replace | monitor } ROUTE\n" \
+       "                       SELECTOR := [root PREFIX] [match PREFIX] [proto RTPROTO]\n" \
+       "                       ROUTE := [TYPE] PREFIX [tos TOS] [proto RTPROTO]" \
+
+#define iprule_trivial_usage \
+       "{[list | add | del] RULE}"
+#define iprule_full_usage \
+       "iprule [list | add | del] SELECTOR ACTION\n" \
+       "       SELECTOR := [from PREFIX] [to PREFIX] [tos TOS] [fwmark FWMARK]\n" \
+       "                       [dev STRING] [pref NUMBER]\n" \
+       "       ACTION := [table TABLE_ID] [nat ADDRESS]\n" \
+       "                       [prohibit | reject | unreachable]\n" \
+       "                       [realms [SRCREALM/]DSTREALM]\n" \
+       "       TABLE_ID := [local | main | default | NUMBER]" \
+
+#define iptunnel_trivial_usage \
+       "{ add | change | del | show } [NAME]\n" \
+       "       [mode { ipip | gre | sit }]\n" \
+       "       [remote ADDR] [local ADDR] [ttl TTL]"
+#define iptunnel_full_usage \
+       "iptunnel { add | change | del | show } [NAME]\n" \
+       "       [mode { ipip | gre | sit }] [remote ADDR] [local ADDR]\n" \
+       "       [[i|o]seq] [[i|o]key KEY] [[i|o]csum]\n" \
+       "       [ttl TTL] [tos TOS] [[no]pmtudisc] [dev PHYS_DEV]" \
+
+#define kbd_mode_trivial_usage \
+       "[-a|k|s|u]"
+#define kbd_mode_full_usage \
+       "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)" \
+
+#define kill_trivial_usage \
+       "[-l] [-signal] process-id..."
+#define kill_full_usage \
+       "Send a signal (default is TERM) to the specified process(es)\n" \
+     "\nOptions:" \
+     "\n       -l      List all signal names and numbers" \
+
+#define kill_example_usage \
+       "$ ps | grep apache\n" \
+       "252 root     root     S [apache]\n" \
+       "263 www-data www-data S [apache]\n" \
+       "264 www-data www-data S [apache]\n" \
+       "265 www-data www-data S [apache]\n" \
+       "266 www-data www-data S [apache]\n" \
+       "267 www-data www-data S [apache]\n" \
+       "$ kill 252\n"
+
+#define killall_trivial_usage \
+       "[-l] [-q] [-signal] process-name..."
+#define killall_full_usage \
+       "Send a signal (default is TERM) to the specified process(es)\n" \
+     "\nOptions:" \
+     "\n       -l      List all signal names and numbers" \
+     "\n       -q      Do not complain if no processes were killed" \
+
+#define killall_example_usage \
+       "$ killall apache\n"
+
+#define killall5_trivial_usage \
+       "[-l] [-signal]"
+#define killall5_full_usage \
+       "Send a signal (default is TERM) to all processes outside current session\n" \
+     "\nOptions:" \
+     "\n       -l      List all signal names and numbers" \
+
+#define klogd_trivial_usage \
+       "[-c n] [-n]"
+#define klogd_full_usage \
+       "Kernel logger\n" \
+     "\nOptions:" \
+     "\n       -c n    Set the default log level of console messages to n" \
+     "\n       -n      Run in foreground" \
+
+#define length_trivial_usage \
+       "STRING"
+#define length_full_usage \
+       "Print STRING's length"
+
+#define length_example_usage \
+       "$ length Hello\n" \
+       "5\n"
+
+#define less_trivial_usage \
+       "[-EMNmh~?] [FILE...]"
+#define less_full_usage \
+       "View a file or list of files. The position within files can be\n" \
+       "changed, and files can be manipulated in various ways.\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       -~      Suppress ~s displayed past the end of the file" \
+
+#define setarch_trivial_usage \
+       "personality program [args...]"
+#define setarch_full_usage \
+       "Personality may be:\n" \
+       "       linux32         Set 32bit uname emulation\n" \
+       "       linux64         Set 64bit uname emulation" \
+
+#define ln_trivial_usage \
+       "[OPTION] TARGET... LINK_NAME|DIRECTORY"
+#define ln_full_usage \
+       "Create a link named LINK_NAME or DIRECTORY to the specified TARGET.\n" \
+       "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
+
+#define load_policy_full_usage
+
+#define loadfont_trivial_usage \
+       "< font"
+#define loadfont_full_usage \
+       "Load a console font from standard input"
+#define loadfont_example_usage \
+       "$ loadfont < /etc/i18n/fontname\n"
+
+#define loadkmap_trivial_usage \
+       "< keymap"
+#define loadkmap_full_usage \
+       "Load a binary keyboard translation table from standard input"
+#define loadkmap_example_usage \
+       "$ loadkmap < /etc/i18n/lang-keymap\n"
+
+#define logger_trivial_usage \
+       "[OPTION]... [MESSAGE]"
+#define logger_full_usage \
+       "Write MESSAGE to the system log. If MESSAGE is omitted, log stdin.\n" \
+     "\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 \
+       "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 \
+       "Print the name of the current user"
+#define logname_example_usage \
+       "$ logname\n" \
+       "root\n"
+
+#define logread_trivial_usage \
+       "[OPTION]..."
+#define logread_full_usage \
+       "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 \
+       "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"
+#define lpd_full_usage \
+       "Example:" \
+     "\n       tcpsvd -E 0 515 softlimit -m 99999 lpd /var/spool"
+
+#define lpq_trivial_usage \
+       "[-P queue[@host[:port]]] [-U USERNAME] [-d JOBID...] [-fs]"
+#define lpq_full_usage \
+       "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 \
+       "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 \
+       "List directory contents\n" \
+     "\nOptions:" \
+     "\n       -1      List files in a single column" \
+     "\n       -A      Do not list implied . and .." \
+     "\n       -a      Do not hide entries starting with ." \
+     "\n       -C      List entries by columns" \
+       USE_FEATURE_LS_TIMESTAMPS( \
+     "\n       -c      With -l: show 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 both full date and full time") \
+       USE_FEATURE_LS_FILETYPES( \
+     "\n       -F      Append indicator (one of */=@|) to entries") \
+     "\n       -i      List the i-node for each file" \
+     "\n       -l      Use a long listing format" \
+     "\n       -n      List numeric UIDs and GIDs instead of names" \
+       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 the listing in reverse order") \
+       USE_FEATURE_LS_SORTFILES( \
+     "\n       -S      Sort the listing 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: show modification time") \
+       USE_FEATURE_LS_TIMESTAMPS( \
+     "\n       -u      With -l: show access time") \
+       USE_FEATURE_LS_SORTFILES( \
+     "\n       -v      Sort the listing by version") \
+       USE_FEATURE_AUTOWIDTH( \
+     "\n       -w NUM  Assume the terminal is NUM columns wide") \
+     "\n       -x      List entries by lines instead of by columns" \
+       USE_FEATURE_LS_SORTFILES( \
+     "\n       -X      Sort the listing by extension") \
+       USE_FEATURE_HUMAN_READABLE( \
+     "\n       -h      Print sizes in human readable format (e.g., 1K 243M 2G)") \
+       USE_SELINUX( \
+     "\n       -k      Print security context") \
+       USE_SELINUX( \
+     "\n       -K      Print security context in long format") \
+       USE_SELINUX( \
+     "\n       -Z      Print security context and permission") \
+
+#define lsattr_trivial_usage \
+       "[-Radlv] [files...]"
+#define lsattr_full_usage \
+       "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      Print long flag names" \
+     "\n       -v      List the file's version/generation number" \
+
+#define lsmod_trivial_usage \
+       ""
+#define lsmod_full_usage \
+       "List the currently loaded kernel modules"
+
+#if ENABLE_FEATURE_MAKEDEVS_LEAF
+#define makedevs_trivial_usage \
+       "NAME TYPE MAJOR MINOR FIRST LAST [s]"
+#define makedevs_full_usage \
+       "Create a range of block or character special files\n\n" \
+       "TYPEs include:\n" \
+       "       b:      Make a block (buffered) device\n" \
+       "       c or u: Make a character (un-buffered) device\n" \
+       "       p:      Make a named pipe. MAJOR and MINOR are ignored for named pipes\n" \
+       "\n" \
+       "FIRST specifies the number appended to NAME to create the first device.\n" \
+       "LAST specifies the number of the last item that should be created\n" \
+       "If 's' is the last argument, the base device is created as well.\n\n" \
+       "For example:\n" \
+       "       makedevs /dev/ttyS c 4 66 2 63   ->  ttyS2-ttyS63\n" \
+       "       makedevs /dev/hda b 3 0 0 8 s    ->  hda,hda1-hda8"
+#define makedevs_example_usage \
+       "# makedevs /dev/ttyS c 4 66 2 63\n" \
+       "[creates ttyS2-ttyS63]\n" \
+       "# makedevs /dev/hda b 3 0 0 8 s\n" \
+       "[creates hda,hda1-hda8]\n"
+#endif
+
+#if ENABLE_FEATURE_MAKEDEVS_TABLE
+#define makedevs_trivial_usage \
+       "[-d device_table] rootdir"
+#define makedevs_full_usage \
+       "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       A regular file\n" \
+       "       d       Directory\n" \
+       "       c       Character special device file\n" \
+       "       b       Block special device file\n" \
+       "       p       Fifo (named pipe)\n" \
+       "uid is the user id for the target file, gid is the group id for the\n" \
+       "target file. The rest of the entries (major, minor, etc) apply to\n" \
+       "to device special files. A '-' may be used for blank entries."
+#define makedevs_example_usage \
+       "For example:\n" \
+       "<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 matchpathcon_trivial_usage \
+       "[-n] [-N] [-f file_contexts_file] [-p prefix] [-V]"
+#define matchpathcon_full_usage \
+       "       -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 \
+       "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " MD5 checksums" \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK( "\n" \
+     "\nOptions:" \
+     "\n       -c      Check MD5 sums against given list" \
+     "\n       -s      Don't output anything, status code shows success" \
+     "\n       -w      Warn about improperly formatted MD5 checksum lines") \
+
+#define md5sum_example_usage \
+       "$ md5sum < busybox\n" \
+       "6fd11e98b98a58f64ff3398d7b324003\n" \
+       "$ md5sum busybox\n" \
+       "6fd11e98b98a58f64ff3398d7b324003  busybox\n" \
+       "$ md5sum -c -\n" \
+       "6fd11e98b98a58f64ff3398d7b324003  busybox\n" \
+       "busybox: OK\n" \
+       "^D\n"
+
+#define mdev_trivial_usage \
+       "[-s]"
+#define mdev_full_usage \
+       "       -s      Scan /sys and populate /dev during system boot\n" \
+       "\n" \
+       "Called with no options (via hotplug) it uses environment variables\n" \
+       "to determine which device to add/remove."
+
+#define mdev_notes_usage "" \
+       USE_FEATURE_MDEV_CONFIG( \
+       "The mdev config file contains lines that look like:\n" \
+       "  hd[a-z][0-9]* 0:3 660\n\n" \
+       "That's device name (with regex match), uid:gid, and permissions.\n\n" \
+       USE_FEATURE_MDEV_EXEC( \
+       "Optionally, that can be followed (on the same line) by a special character\n" \
+       "and a command line to run after creating/before deleting the corresponding\n" \
+       "device(s). The environment variable $MDEV indicates the active device node\n" \
+       "(which is useful if it's a regex match). For example:\n\n" \
+       "  hdc root:cdrom 660  *ln -s $MDEV cdrom\n\n" \
+       "The special characters are @ (run after creating), $ (run before deleting),\n" \
+       "and * (run both after creating and before deleting). The commands run in\n" \
+       "the /dev directory, and use system() which calls /bin/sh.\n\n" \
+       ) \
+       "Config file parsing stops on the first matching line. If no config\n" \
+       "entry is matched, devices are created with default 0:0 660. (Make\n" \
+       "the last line match .* to override this.)\n\n" \
+       )
+
+#define mesg_trivial_usage \
+       "[y|n]"
+#define mesg_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "       -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 \
+       "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 \
+       "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 mknod_trivial_usage \
+       "[OPTIONS] NAME TYPE MAJOR MINOR"
+#define mknod_full_usage \
+       "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 \
+       "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 \
+       "Create a temporary file with its name based on TEMPLATE.\n" \
+       "TEMPLATE is any name with six 'Xs' (i.e., /tmp/temp.XXXXXX).\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" \
+     "\n" \
+       "For -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 \
+       "Options:" \
+     "\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" \
+
+#define modprobe_notes_usage \
+"modprobe can (un)load a stack of modules, passing each module options (when\n" \
+"loading). modprobe uses a configuration file to determine what option(s) to\n" \
+"pass each module it loads.\n" \
+"\n" \
+"The configuration file is searched (in order) amongst:\n" \
+"\n" \
+"    /etc/modprobe.conf (2.6 only)\n" \
+"    /etc/modules.conf\n" \
+"    /etc/conf.modules (deprecated)\n" \
+"\n" \
+"They all have the same syntax (see below). If none is present, it is\n" \
+"_not_ an error; each loaded module is then expected to load without\n" \
+"options. Once a file is found, the others are tested for.\n" \
+"\n" \
+"/etc/modules.conf entry format:\n" \
+"\n" \
+"  alias <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 \
+       "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 options,more-options]"
+#define mount_full_usage \
+       "Mount a filesystem. Filesystem autodetection requires /proc be mounted.\n" \
+     "\nOptions:" \
+     "\n       -a              Mount all filesystems in fstab" \
+       USE_FEATURE_MOUNT_FAKE( \
+     "\n       -f              "USE_FEATURE_MTAB_SUPPORT("Update /etc/mtab, but ")"don't mount" \
+       ) \
+       USE_FEATURE_MTAB_SUPPORT( \
+     "\n       -n              Don't update /etc/mtab" \
+       ) \
+     "\n       -r              Read-only mount" \
+     "\n       -t fs-type      Filesystem type" \
+     "\n       -w              Read-write mount (default)" \
+       "\n" \
+       "-o option:\n" \
+       USE_FEATURE_MOUNT_LOOP( \
+       "       loop            Ignored (loop devices are autodetected)\n" \
+       ) \
+       USE_FEATURE_MOUNT_FLAGS( \
+       "       [a]sync         Writes are asynchronous / synchronous\n" \
+       "       [no]atime       Disable / enable updates to inode access times\n" \
+       "       [no]diratime    Disable / enable atime updates to directories\n" \
+       "       [no]dev         Allow use of special device files / disallow them\n" \
+       "       [no]exec        Allow use of executable files / disallow them\n" \
+       "       [no]suid        Allow set-user-id-root programs / disallow them\n" \
+       "       [r]shared       Convert [recursively] to a shared subtree\n" \
+       "       [r]slave        Convert [recursively] to a slave subtree\n" \
+       "       [r]private      Convert [recursively] to a private subtree\n" \
+       "       [un]bindable    Make mount point [un]able to be bind mounted\n" \
+       "       bind            Bind a directory to an additional location\n" \
+       "       move            Relocate an existing mount point\n" \
+       ) \
+       "       remount         Remount a mounted filesystem, changing its flags\n" \
+       "       ro/rw           Mount for read-only / read-write\n" \
+       "\n" \
+       "There are EVEN MORE flags that are specific to each filesystem\n" \
+       "You'll have to see the written documentation for those filesystems" \
+
+#define mount_example_usage \
+       "$ mount\n" \
+       "/dev/hda3 on / type minix (rw)\n" \
+       "proc on /proc type proc (rw)\n" \
+       "devpts on /dev/pts type devpts (rw)\n" \
+       "$ mount /dev/fd0 /mnt -t msdos -o ro\n" \
+       "$ mount /tmp/diskimage /opt -t ext2 -o loop\n" \
+       "$ mount cd_image.iso mydir\n"
+#define mount_notes_usage \
+       "Returns 0 for success, number of failed mounts for -a, or errno for one mount."
+
+#define mountpoint_trivial_usage \
+       "[-q] <[-d] DIR | -x DEVICE>"
+#define mountpoint_full_usage \
+       "mountpoint checks if the directory is a mountpoint\n" \
+     "\nOptions:" \
+     "\n       -q      Quiet" \
+     "\n       -d      Print major/minor device number of the filesystem" \
+     "\n       -x      Print major/minor device number of the blockdevice" \
+
+#define mountpoint_example_usage \
+       "$ mountpoint /proc\n" \
+       "/proc is not a mountpoint\n" \
+       "$ mountpoint /sys\n" \
+       "/sys is a mountpoint\n"
+
+#define mt_trivial_usage \
+       "[-f device] opcode value"
+#define mt_full_usage \
+       "Control magnetic tape drive operation\n" \
+       "\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 \
+       "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 \
+       "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 \
+       "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 \
+       "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")"]"
+#define netstat_full_usage \
+       "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" \
+       )
+
+#define nice_trivial_usage \
+       "[-n ADJUST] [COMMAND [ARG]...]"
+#define nice_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "VTNUM COMMAND [ARGS...]"
+#define openvt_full_usage \
+       "Start a command on a new virtual terminal"
+#define openvt_example_usage \
+       "openvt 2 /bin/ash\n"
+
+#define passwd_trivial_usage \
+       "[OPTION] [name]"
+#define passwd_full_usage \
+       "Change 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|--encrypt]") SKIP_GETOPT_LONG("[-m|-e]")
+#define chpasswd_full_usage \
+       "Read user:password information from stdin\n" \
+       "and update /etc/passwd accordingly.\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -e,--encrypt    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]"
+#define patch_full_usage \
+       "       -p NUM  Strip NUM leading components from file names" \
+     "\n       -i DIFF Read DIFF instead of stdin" \
+
+#define patch_example_usage \
+       "$ patch -p1 < example.diff\n" \
+       "$ patch -p0 -i example.diff"
+
+#define pgrep_trivial_usage \
+       "[-flnovx] pattern"
+#define pgrep_full_usage \
+       "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 \
+       "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 \
+       "Send ICMP ECHO_REQUEST packets to network hosts"
+#define ping6_trivial_usage \
+       "host"
+#define ping6_full_usage \
+       "Send ICMP ECHO_REQUEST packets to network hosts"
+#else
+#define ping_trivial_usage \
+       "[OPTION]... host"
+#define ping_full_usage \
+       "Send ICMP ECHO_REQUEST packets to network hosts\n" \
+     "\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       -q              Quiet, only displays output at start" \
+     "\n                       and when finished" \
+
+#define ping6_trivial_usage \
+       "[OPTION]... host"
+#define ping6_full_usage \
+       "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 pivot_root_trivial_usage \
+       "NEW_ROOT PUT_OLD"
+#define pivot_root_full_usage \
+       "Move the current root file system to PUT_OLD and make NEW_ROOT\n" \
+       "the new root file system"
+
+#define pkill_trivial_usage \
+       "[-l] | [-fnovx] [-signal] pattern"
+#define pkill_full_usage \
+       "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 poweroff_trivial_usage \
+       "[-d delay] [-n] [-f]"
+#define poweroff_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "[-p MIN_PORT] [-P MAX_PORT] [-t TIMEOUT] [-T MIN_RTT] HOST"
+#define pscan_full_usage \
+       "Scan a host, print all open ports\n" \
+     "\nOptions:" \
+     "\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 \
+       "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 \
+       "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 \
+       "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 readahead_trivial_usage \
+       "[FILE]..."
+#define readahead_full_usage \
+       "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("[-f] ") "FILE"
+#define readlink_full_usage \
+       "Display the value of a symlink" \
+       USE_FEATURE_READLINK_FOLLOW( "\n" \
+     "\nOptions:" \
+     "\n       -f      Canonicalize by following all symlinks") \
+
+#define readprofile_trivial_usage \
+       "[OPTIONS]..."
+#define readprofile_full_usage \
+       "Options:" \
+     "\n       -m 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 \
+       "Return the absolute pathnames of given argument"
+
+#define reboot_trivial_usage \
+       "[-d delay] [-n] [-f]"
+#define reboot_full_usage \
+       "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 renice_trivial_usage \
+       "{{-n INCREMENT} | PRIORITY} [[-p | -g | -u] ID...]"
+#define renice_full_usage \
+       "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 \
+       "Reset the screen"
+
+#define resize_trivial_usage \
+       ""
+#define resize_full_usage \
+       "Resize the screen"
+
+#define restorecon_trivial_usage \
+       "[-iFnrRv] [-e excludedir]... [-o filename] [-f filename | pathname]"
+#define restorecon_full_usage \
+       "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 \
+       "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 \
+       "Remove the DIRECTORY, if it is empty"
+#define rmdir_example_usage \
+       "# rmdir /tmp/foo\n"
+
+#define rmmod_trivial_usage \
+       "[OPTION]... [MODULE]..."
+#define rmmod_full_usage \
+       "Unload the specified kernel modules from the kernel\n" \
+     "\nOptions:" \
+     "\n       -a      Remove all unused modules (recursively)" \
+
+#define rmmod_example_usage \
+       "$ rmmod tulip\n"
+
+#define route_trivial_usage \
+       "[{add|del|delete}]"
+#define route_full_usage \
+       "Edit the kernel's routing tables\n" \
+     "\nOptions:" \
+     "\n       -n      Dont resolve names" \
+     "\n       -e      Display other/more information" \
+     "\n       -A inet" USE_FEATURE_IPV6("{6}") "      Select address family" \
+
+#define rpm_trivial_usage \
+       "-i -q[ildc]p package.rpm"
+#define rpm_full_usage \
+       "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 \
+       "Output a cpio archive of the rpm file"
+
+#define rtcwake_trivial_usage \
+       "[-a | -l | -u] [-d DEV] [-m MODE] [-s SECS | -t TIME]"
+#define rtcwake_full_usage \
+       "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 \
+       "Run a program in a different security context\n" \
+     "\n       CONTEXT         Complete security context\n" \
+       USE_GETOPT_LONG( \
+     "\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_GETOPT_LONG( \
+     "\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 \
+       "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 \
+       "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 \
+       "Start and monitor a service and optionally an appendant log service"
+
+#define runsvdir_trivial_usage \
+       "[-P] dir"
+#define runsvdir_full_usage \
+       "Start a runsv process for each subdirectory"
+
+#define rx_trivial_usage \
+       "FILE"
+#define rx_full_usage \
+       "Receive a file using the xmodem protocol"
+#define rx_example_usage \
+       "$ rx /tmp/foo\n"
+
+#define script_trivial_usage \
+       "[-afq] [-c COMMAND] [OUTFILE]"
+#define script_full_usage \
+       "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 \
+       "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
+#define selinuxenabled_full_usage
+
+#define sendmail_trivial_usage \
+       "[-w timeout] [-U user] [-P password] [-X]\n" \
+       "-t to [-t to]... [-n] [-s subject] [-c charset] server[:port] from [body] [attachment ...]"
+#define sendmail_full_usage \
+       "Send an email.\n" \
+     "\nOptions:" \
+     "\n       -w timeout      Set timeout on network operations" \
+     "\n       -U username     Authenticate with specified username/password" \
+     "\n       -P password" \
+     "\n       -t address      Recipient(s). May be repeated" \
+     "\n       -X              Use openssl connection helper for secured servers" \
+     "\n       -n              Request delivery notification to sender" \
+     "\n       -s subject      Subject" \
+     "\n       -c charset      Assumed charset for body and subject [utf-8]" \
+
+#define seq_trivial_usage \
+       "[first [increment]] last"
+#define seq_full_usage \
+       "Print numbers from FIRST to LAST, in steps of INCREMENT.\n" \
+       "FIRST, INCREMENT default to 1" \
+       "\n\nArguments:\n" \
+       "       LAST\n" \
+       "       FIRST LAST\n" \
+       "       FIRST INCREMENT LAST"
+
+#define sestatus_trivial_usage \
+       "[-vb]"
+#define sestatus_full_usage \
+       "       -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 \
+       "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 \
+       "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 setkeycodes_trivial_usage \
+       "SCANCODE KEYCODE..."
+#define setkeycodes_full_usage \
+       "Set entries into the kernel's scancode-to-keycode map,\n" \
+       "allowing unusual keyboards to generate usable keycodes.\n\n" \
+       "SCANCODE may be either xx or e0xx (hexadecimal),\n" \
+       "and KEYCODE is given in decimal" \
+
+#define setkeycodes_example_usage \
+       "$ setkeycodes e030 127\n"
+
+#define setlogcons_trivial_usage \
+       "N"
+#define setlogcons_full_usage \
+       "Redirect the kernel output to console N (0 for current)"
+
+#define setsebool_trivial_usage \
+       "boolean value"
+
+#define setsebool_full_usage \
+       "Change boolean setting"
+
+#define setsid_trivial_usage \
+       "PROG [ARG...]"
+#define setsid_full_usage \
+       "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 \
+       "lash is deprecated, please use hush"
+
+#define last_trivial_usage \
+       ""
+#define last_full_usage \
+       "Show listing of the last users that logged into the system"
+
+#define sha1sum_trivial_usage \
+       "[OPTION] [FILEs...]" \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK("\n   or: sha1sum [OPTION] -c [FILE]")
+#define sha1sum_full_usage \
+       "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " SHA1 checksums." \
+       USE_FEATURE_MD5_SHA1_SUM_CHECK( "\n" \
+     "\nOptions:" \
+     "\n       -c      Check SHA1 sums against given list" \
+     "\n       -s      Don't output anything, status code shows success" \
+     "\n       -w      Warn about improperly formatted SHA1 checksum lines" \
+       )
+
+#define slattach_trivial_usage \
+       "[-cehmLF] [-s speed] [-p protocol] DEVICEs"
+#define slattach_full_usage \
+       "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 \
+       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 \
+       "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 \
+       "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] [" \
+       USE_GETOPT_LONG("--start|--stop") SKIP_GETOPT_LONG("-S|-K") \
+       "] ... [-- arguments...]"
+#define start_stop_daemon_full_usage \
+       "Start and stop services\n" \
+     "\nOptions:" \
+       USE_GETOPT_LONG( \
+     "\n       -S,--start              Start" \
+     "\n       -K,--stop               Stop" \
+     "\n       -a,--startas pathname   Start process specified by pathname" \
+     "\n       -b,--background         Put process into background" \
+     "\n       -u,--user username|uid  Stop this user's processes" \
+     "\n       -x,--exec executable    Program to either start or check" \
+     "\n       -n,--name process-name  Stop processes with this name" \
+     "\n       -p,--pidfile pid-file   Save or load pid using a pid-file" \
+     "\n       -m,--make-pidfile       Create the -p file and enter pid in it" \
+     "\n       -q,--quiet              Quiet" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -o,--oknodo             Exit status 0 if nothing done" \
+     "\n       -v,--verbose            Verbose" \
+     "\n       -N,--nicelevel N        Add N to process's nice level" \
+       ) \
+     "\n       -s,--signal signal      Signal to send (default TERM)" \
+     "\n       -c,--chuid user[:[grp]] Change to specified user/group" \
+       ) \
+       SKIP_GETOPT_LONG( \
+     "\n       -S              Start" \
+     "\n       -K              Stop" \
+     "\n       -a pathname     Start process specified by pathname" \
+     "\n       -b              Put process into background" \
+     "\n       -u username|uid Stop this user's processes" \
+     "\n       -x executable   Program to either start or check" \
+     "\n       -n process-name Stop processes with this name" \
+     "\n       -p pid-file     Save or load pid using a pid-file" \
+     "\n       -m              Create the -p file and enter pid in it" \
+     "\n       -q              Quiet" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -o              Exit status 0 if nothing done" \
+     "\n       -v              Verbose" \
+     "\n       -N N            Add N to process's nice level" \
+       ) \
+     "\n       -s signal       Signal to send (default TERM)" \
+     "\n       -c user[:[grp]] Change to specified user/group" \
+       )
+
+#define stat_trivial_usage \
+       "[OPTION] FILE..."
+#define stat_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "Single user login\n" \
+     "\nOptions:" \
+     "\n       -t      Timeout" \
+
+#define sum_trivial_usage \
+       "[rs] [files...]"
+#define sum_full_usage \
+       "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 \
+       "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 \
+       "Continuously read log data from standard input, optionally " \
+       "filter log messages, and write the data to one or more automatically " \
+       "rotated logs" \
+
+#define swapoff_trivial_usage \
+       "[-a] [DEVICE]"
+#define swapoff_full_usage \
+       "Stop swapping on DEVICE\n" \
+     "\nOptions:" \
+     "\n       -a      Stop swapping on all swap devices" \
+
+#define swapon_trivial_usage \
+       "[-a] [DEVICE]"
+#define swapon_full_usage \
+       "Start swapping on DEVICE\n" \
+     "\nOptions:" \
+     "\n       -a      Start swapping on all swap devices" \
+
+#define switch_root_trivial_usage \
+       "[-c /dev/console] NEW_ROOT NEW_INIT [ARGUMENTS_TO_INIT]"
+#define switch_root_full_usage \
+       "Use from PID 1 under initramfs to free initramfs, chroot to NEW_ROOT,\n" \
+       "and exec NEW_INIT\n" \
+     "\nOptions:" \
+     "\n       -c      Redirect console to device on new root" \
+
+#define sync_trivial_usage \
+       ""
+#define sync_full_usage \
+       "Write all buffered filesystem blocks to disk"
+
+#define sysctl_trivial_usage \
+       "[OPTIONS]... [VALUE]..."
+#define sysctl_full_usage \
+       "Configure kernel parameters at runtime\n" \
+     "\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 \
+       "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 \
+       "Concatenate FILE(s) and print them in reverse"
+
+#define tail_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define tail_full_usage \
+       "Print last 10 lines of each FILE to standard output.\n" \
+       "With more than one FILE, precede each with a header giving the\n" \
+       "file name. With no FILE, or when FILE is -, read standard input.\n" \
+     "\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\n" \
+       "If the first character of N (bytes or lines) is a '+', output begins with\n" \
+       "the Nth item from the start of each file, otherwise, print the last N items\n" \
+       "in the file. N bytes may be suffixed by k (x1024), b (x512), or m (1024^2)." ) \
+
+#define tail_example_usage \
+       "$ tail -n 1 /etc/resolv.conf\n" \
+       "nameserver 10.0.0.1\n"
+
+#define tar_trivial_usage \
+       "-[" USE_FEATURE_TAR_CREATE("c") USE_FEATURE_TAR_GZIP("z") \
+       USE_FEATURE_TAR_BZIP2("j") USE_FEATURE_TAR_LZMA("a") \
+       USE_FEATURE_TAR_COMPRESS("Z") "xtvO] " \
+       USE_FEATURE_TAR_FROM("[-X FILE] ") \
+       "[-f TARFILE] [-C DIR] [FILE(s)]..."
+#define tar_full_usage \
+       "Create, extract, or list files from a tar file\n" \
+     "\nOptions:" \
+       USE_FEATURE_TAR_CREATE( \
+     "\n       c       Create") \
+     "\n       x       Extract" \
+     "\n       t       List" \
+     "\nArchive format selection:" \
+       USE_FEATURE_TAR_GZIP( \
+     "\n       z       Filter the archive through gzip" \
+       ) \
+       USE_FEATURE_TAR_BZIP2( \
+     "\n       j       Filter the archive through bzip2" \
+       ) \
+       USE_FEATURE_TAR_LZMA( \
+     "\n       a       Filter the archive through lzma" \
+       ) \
+       USE_FEATURE_TAR_COMPRESS( \
+     "\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 \
+       "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 \
+       "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 \
+       "Connect to telnet server\n" \
+     "\nOptions:" \
+     "\n       -a      Attempt an automatic login with USER variable" \
+     "\n       -l USER Attempt an automatic login with USER argument" \
+
+#else
+#define telnet_trivial_usage \
+       "HOST [PORT]"
+#define telnet_full_usage \
+       "Connect to telnet server"
+#endif
+
+#define telnetd_trivial_usage \
+       "[OPTION]"
+#define telnetd_full_usage \
+       "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" \
+       )
+
+#define test_trivial_usage \
+       "EXPRESSION\n" \
+       "  or   [ EXPRESSION ]"
+#define test_full_usage \
+       "Check file types and compares values returning an exit code\n" \
+       "determined by the value of EXPRESSION"
+#define test_example_usage \
+       "$ test 1 -eq 2\n" \
+       "$ echo $?\n" \
+       "1\n" \
+       "$ test 1 -eq 1\n" \
+       "$ echo $?\n" \
+       "0\n" \
+       "$ [ -d /etc ]\n" \
+       "$ echo $?\n" \
+       "0\n" \
+       "$ [ -d /junk ]\n" \
+       "$ echo $?\n" \
+       "1\n"
+
+#define 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 \
+       "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 \
+       "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 \
+       "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 \
+       "Transfer a file on tftp client's request.\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 \
+       "Run the program COMMAND with arguments ARGS. When COMMAND finishes,\n" \
+       "COMMAND's resource usage information is displayed.\n" \
+     "\nOptions:" \
+     "\n       -v      Verbose" \
+
+#define top_trivial_usage \
+       "[-b] [-n COUNT] [-d SECONDS]"
+#define top_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "Return an exit code of TRUE (0)"
+#define true_example_usage \
+       "$ true\n" \
+       "$ echo $?\n" \
+       "0\n"
+
+#define tty_trivial_usage \
+       ""
+#define tty_full_usage \
+       "Print 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 \
+       "Print dimension(s) of standard input's terminal, on error return 80x25"
+
+#define tune2fs_trivial_usage \
+       "[-c max-mounts-count] [-e errors-behavior] [-g group] " \
+       "[-i interval[d|m|w]] [-j] [-J journal-options] [-l] [-s sparse-flag] " \
+       "[-m reserved-blocks-percent] [-o [^]mount-options[,...]] " \
+       "[-r reserved-blocks-count] [-u user] [-C mount-count] " \
+       "[-L volume-label] [-M last-mounted-dir] [-O [^]feature[,...]] " \
+       "[-T last-check-time] [-U UUID] device"
+#define tune2fs_full_usage \
+       "Adjust filesystem options on ext[23] filesystems"
+
+#define udhcpc_trivial_usage \
+       "[-Cfbnqtv] [-c CID] [-V VCLS] [-H HOSTNAME] [-i INTERFACE]\n" \
+       "       [-p pidfile] [-r IP] [-s script] [-O dhcp-option]..." USE_FEATURE_UDHCP_PORT(" [-P N]")
+#define udhcpc_full_usage \
+       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 /usr/share/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       -f,--foreground Run in foreground" \
+     "\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" \
+     "\n       -O,--request-option=OPT Request DHCP option OPT from server" \
+       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 /usr/share/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       -f              Run in foreground" \
+     "\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" \
+     "\n       -O OPT          Request DHCP option OPT from server" \
+       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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "[-fscdu]... [INPUT [OUTPUT]]"
+#define uniq_full_usage \
+       "Discard all but one of successive identical lines from INPUT\n" \
+       "(or standard input), writing to OUTPUT (or standard output)\n" \
+     "\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 the first N fields" \
+     "\n       -s N    Skip the first N chars (after any skipped fields)" \
+
+#define uniq_example_usage \
+       "$ echo -e \"a\\na\\nb\\nc\\nc\\na\" | sort | uniq\n" \
+       "a\n" \
+       "b\n" \
+       "c\n"
+
+#define unix2dos_trivial_usage \
+       "[option] [FILE]"
+#define unix2dos_full_usage \
+       "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 \
+       "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 \
+       "Display the time since the last boot"
+
+#define uptime_example_usage \
+       "$ uptime\n" \
+       "  1:55pm  up  2:30, load average: 0.09, 0.04, 0.00\n"
+
+#define usleep_trivial_usage \
+       "N"
+#define usleep_full_usage \
+       "Pause for N microseconds"
+
+#define usleep_example_usage \
+       "$ usleep 1000000\n" \
+       "[pauses for 1 second]\n"
+
+#define uudecode_trivial_usage \
+       "[-o outfile] [infile]"
+#define uudecode_full_usage \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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 \
+       "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]] [-F] DEV"
+#define watchdog_full_usage \
+       "Periodically write to watchdog device DEV\n" \
+     "\nOptions:" \
+     "\n       -t N    Timer period (default 30)" \
+     "\n       -F      Run in foreground" \
+     "\n" \
+     "\nUse -t 500ms to specify period in milliseconds" \
+
+#define wc_trivial_usage \
+       "[OPTION]... [FILE]..."
+#define wc_full_usage \
+       "Print line, word, and byte counts for each FILE, and a total line if\n" \
+       "more than one FILE is specified. With no FILE, read standard input.\n" \
+     "\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_GETOPT_LONG( \
+       "[-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_GETOPT_LONG( \
+       "[-csq] [-O file] [-Y on/off] [-P DIR] [-U agent] url" \
+       )
+#define wget_full_usage \
+       "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 \
+       "Locate a COMMAND"
+#define which_example_usage \
+       "$ which login\n" \
+       "/bin/login\n"
+
+#define who_trivial_usage \
+       "[-a]"
+#define who_full_usage \
+       "Show who is logged on\n" \
+     "\nOptions:" \
+     "\n       -a      show all" \
+
+#define whoami_trivial_usage \
+       ""
+#define whoami_full_usage \
+       "Print the user name associated with the current effective user id"
+
+#define xargs_trivial_usage \
+       "[OPTIONS] [COMMAND] [ARGS...]"
+#define xargs_full_usage \
+       "Execute COMMAND on every item given by standard input\n" \
+     "\nOptions:" \
+       USE_FEATURE_XARGS_SUPPORT_CONFIRMATION( \
+     "\n       -p      Prompt the user about whether to run each command") \
+     "\n       -r      Do not run command for empty read lines" \
+       USE_FEATURE_XARGS_SUPPORT_TERMOPT( \
+     "\n       -x      Exit if the size is exceeded") \
+       USE_FEATURE_XARGS_SUPPORT_ZERO_TERM( \
+     "\n       -0      Input filenames are terminated by a null character") \
+     "\n       -t      Print the command line on stderr before executing it" \
+
+#define xargs_example_usage \
+       "$ ls | xargs gzip\n" \
+       "$ find . -name '*.c' -print | xargs rm\n"
+
+#define yes_trivial_usage \
+       "[OPTION]... [STRING]..."
+#define yes_full_usage \
+       "Repeatedly output a line with all specified STRING(s), or 'y'"
+
+#define zcat_trivial_usage \
+       "FILE"
+#define zcat_full_usage \
+       "Uncompress to stdout"
+
+#define zcip_trivial_usage \
+       "[OPTIONS] ifname script"
+#define zcip_full_usage \
+       "Manage a ZeroConf IPv4 link-local address\n" \
+     "\nOptions:" \
+     "\n       -f              Run in foreground" \
+     "\n       -q              Quit after address (no daemon)" \
+     "\n       -r 169.254.x.x  Request this address first" \
+     "\n       -v              Verbose" \
+
+#endif /* __BB_USAGE_H__ */
diff --git a/include/volume_id.h b/include/volume_id.h
new file mode 100644 (file)
index 0000000..99cb11f
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * 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);
diff --git a/include/xatonum.h b/include/xatonum.h
new file mode 100644 (file)
index 0000000..49ddced
--- /dev/null
@@ -0,0 +1,168 @@
+/* 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.
+ */
+
+/* Provides extern declarations of functions */
+#define DECLARE_STR_CONV(type, T, UT) \
+\
+unsigned type xstrto##UT##_range_sfx(const char *str, int b, unsigned type l, unsigned type u, const struct suffix_mult *sfx); \
+unsigned type xstrto##UT##_range(const char *str, int b, unsigned type l, unsigned type u); \
+unsigned type xstrto##UT##_sfx(const char *str, int b, const struct suffix_mult *sfx); \
+unsigned type xstrto##UT(const char *str, int b); \
+unsigned type xato##UT##_range_sfx(const char *str, unsigned type l, unsigned type u, const struct suffix_mult *sfx); \
+unsigned type xato##UT##_range(const char *str, unsigned type l, unsigned type u); \
+unsigned type xato##UT##_sfx(const char *str, const struct suffix_mult *sfx); \
+unsigned type xato##UT(const char *str); \
+type xstrto##T##_range_sfx(const char *str, int b, type l, type u, const struct suffix_mult *sfx); \
+type xstrto##T##_range(const char *str, int b, type l, type u); \
+type xato##T##_range_sfx(const char *str, type l, type u, const struct suffix_mult *sfx); \
+type xato##T##_range(const char *str, type l, type u); \
+type xato##T##_sfx(const char *str, const struct suffix_mult *sfx); \
+type xato##T(const char *str); \
+
+/* Unsigned long long functions always exist */
+DECLARE_STR_CONV(long long, ll, ull)
+
+
+/* Provides 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 othervise ok
+ *    Return value is still valid, caller should just check whether end[0]
+ *    is a valid terminating char for particular case. OTOH, if caller
+ *    requires '\0' terminated input, [s]he can just check errno == 0.
+ * errno = ERANGE if value had alphanumeric terminating char ("1234abcg").
+ * errno = ERANGE if value is out of range, missing, etc.
+ * errno = ERANGE if value had minus sign for strtouXX (even "-0" is not ok )
+ *    return value is all-ones in this case.
+ */
+
+unsigned long long bb_strtoull(const char *arg, char **endp, int base);
+long long bb_strtoll(const char *arg, char **endp, int base);
+
+#if ULONG_MAX == ULLONG_MAX
+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);
+long bb_strtol(const char *arg, char **endp, int base);
+#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);
+int bb_strtoi(const char *arg, char **endp, int base);
+#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); */
diff --git a/include/xregex.h b/include/xregex.h
new file mode 100644 (file)
index 0000000..23cf19c
--- /dev/null
@@ -0,0 +1,18 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Busybox xregcomp utility routine.  This isn't in libbb.h because the
+ * C library we're linking against may not support regex.h.
+ *
+ * Based in part on code from sash, Copyright (c) 1999 by David I. Bell
+ * Permission has been granted to redistribute this code under the GPL.
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+#ifndef __BB_REGEX__
+#define __BB_REGEX__
+
+#include <regex.h>
+char* regcomp_or_errmsg(regex_t *preg, const char *regex, int cflags);
+void xregcomp(regex_t *preg, const char *regex, int cflags);
+
+#endif
diff --git a/init/Config.in b/init/Config.in
new file mode 100644 (file)
index 0000000..25f4390
--- /dev/null
@@ -0,0 +1,112 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Init Utilities"
+
+config INIT
+       bool "init"
+       default n
+       select FEATURE_SYSLOG
+       help
+         init is the first program run when the system boots.
+
+config DEBUG_INIT
+       bool "Debugging aid"
+       default n
+       depends on INIT
+       help
+         Turn this on to disable all the dangerous
+         rebooting stuff when debugging.
+
+config FEATURE_USE_INITTAB
+       bool "Support reading an inittab file"
+       default y
+       depends on INIT
+       help
+         Allow init to read an inittab file when the system boot.
+
+config FEATURE_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
+       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 can actually kill
+         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 y
+       help
+         Stop all processes and either halt, reboot, or power off the system.
+
+config MESG
+       bool "mesg"
+       default y
+       help
+         Mesg controls access to your terminal by others.  It is typically
+         used to allow or disallow other users to write to your terminal
+
+endmenu
diff --git a/init/Kbuild b/init/Kbuild
new file mode 100644 (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..c14f0f2
--- /dev/null
@@ -0,0 +1,93 @@
+/* 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>
+#endif
+
+int halt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int halt_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       static const int magic[] = {
+#ifdef RB_HALT_SYSTEM
+RB_HALT_SYSTEM,
+#elif defined RB_HALT
+RB_HALT,
+#endif
+#ifdef RB_POWER_OFF
+RB_POWER_OFF,
+#elif defined RB_POWERDOWN
+RB_POWERDOWN,
+#endif
+RB_AUTOBOOT
+       };
+       static const smallint signals[] = { SIGUSR1, SIGUSR2, SIGTERM };
+
+       int delay = 0;
+       int which, flags, rc = 1;
+#if ENABLE_FEATURE_WTMP
+       struct utmp utmp;
+       struct timeval tv;
+       struct utsname uts;
+#endif
+
+       /* Figure out which applet we're running */
+       for (which = 0; "hpr"[which] != *applet_name; which++)
+               continue;
+
+       /* Parse and handle arguments */
+       opt_complementary = "d+"; /* -d N */
+       flags = getopt32(argv, "d:nfw", &delay);
+
+       sleep(delay);
+
+#if ENABLE_FEATURE_WTMP
+       if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) {
+               close(creat(bb_path_wtmp_file, 0664));
+       }
+       memset(&utmp, 0, sizeof(utmp));
+       gettimeofday(&tv, NULL);
+       utmp.ut_tv.tv_sec = tv.tv_sec;
+       utmp.ut_tv.tv_usec = tv.tv_usec;
+       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);
+#endif /* !ENABLE_FEATURE_WTMP */
+
+       if (flags & 8) /* -w */
+               return 0;
+       if (!(flags & 2)) /* no -n */
+               sync();
+
+       /* Perform action. */
+       if (ENABLE_INIT && !(flags & 4)) {
+               if (ENABLE_FEATURE_INITRD) {
+                       pid_t *pidlist = find_pid_by_name("linuxrc");
+                       if (pidlist[0] > 0)
+                               rc = kill(pidlist[0], signals[which]);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               free(pidlist);
+               }
+               if (rc)
+                       rc = kill(1, signals[which]);
+       } else
+               rc = reboot(magic[which]);
+
+       if (rc)
+               bb_error_msg("no");
+       return rc;
+}
diff --git a/init/init.c b/init/init.c
new file mode 100644 (file)
index 0000000..a6c73e3
--- /dev/null
@@ -0,0 +1,994 @@
+/* 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>
+
+#define COMMAND_SIZE 256
+#define CONSOLE_NAME_SIZE 32
+#define MAXENV 16              /* Number of env. vars */
+
+/*
+ * When a file named CORE_ENABLE_FLAG_FILE exists, setrlimit is called
+ * before processes are spawned to set core file size as unlimited.
+ * This is for debugging only.  Don't use this is production, unless
+ * you want core dumps lying about....
+ */
+#define CORE_ENABLE_FLAG_FILE "/.init_enable_core"
+#include <sys/resource.h>
+
+#define INITTAB      "/etc/inittab"    /* inittab file location */
+#ifndef INIT_SCRIPT
+#define INIT_SCRIPT  "/etc/init.d/rcS" /* Default sysinit script. */
+#endif
+
+/* Allowed init action types */
+#define SYSINIT     0x01
+#define RESPAWN     0x02
+/* like respawn, but wait for <Enter> to be pressed on tty: */
+#define ASKFIRST    0x04
+#define WAIT        0x08
+#define ONCE        0x10
+#define CTRLALTDEL  0x20
+#define SHUTDOWN    0x40
+#define RESTART     0x80
+
+#define STR_SYSINIT     "\x01"
+#define STR_RESPAWN     "\x02"
+#define STR_ASKFIRST    "\x04"
+#define STR_WAIT        "\x08"
+#define STR_ONCE        "\x10"
+#define STR_CTRLALTDEL  "\x20"
+#define STR_SHUTDOWN    "\x40"
+#define STR_RESTART     "\x80"
+
+/* Set up 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 variables */
+static struct init_action *init_action_list = NULL;
+
+static const char *log_console = VC_5;
+static sig_atomic_t got_cont = 0;
+
+enum {
+       L_LOG = 0x1,
+       L_CONSOLE = 0x2,
+
+#if ENABLE_FEATURE_EXTRA_QUIET
+       MAYBE_CONSOLE = 0x0,
+#else
+       MAYBE_CONSOLE = L_CONSOLE,
+#endif
+
+#ifndef RB_HALT_SYSTEM
+       RB_HALT_SYSTEM = 0xcdef0123, /* FIXME: this overflows enum */
+       RB_ENABLE_CAD = 0x89abcdef,
+       RB_DISABLE_CAD = 0,
+       RB_POWER_OFF = 0x4321fedc,
+       RB_AUTOBOOT = 0x01234567,
+#endif
+};
+
+static const char *const environment[] = {
+       "HOME=/",
+       bb_PATH_root_path,
+       "SHELL=/bin/sh",
+       "USER=root",
+       NULL
+};
+
+/* Function prototypes */
+static void delete_init_action(struct init_action *a);
+static void halt_reboot_pwoff(int sig) ATTRIBUTE_NORETURN;
+
+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 (wait(NULL) != pid)
+               continue;
+}
+
+static void loop_forever(void) ATTRIBUTE_NORETURN;
+static void loop_forever(void)
+{
+       while (1)
+               sleep(1);
+}
+
+/* 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 messageD(...) do { if (ENABLE_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, ...)
+{
+       static int log_fd = -1;
+       va_list arguments;
+       int l;
+       char msg[128];
+
+       msg[0] = '\r';
+       va_start(arguments, fmt);
+       vsnprintf(msg + 1, sizeof(msg) - 2, fmt, arguments);
+       va_end(arguments);
+       msg[sizeof(msg) - 2] = '\0';
+       l = strlen(msg);
+
+       if (ENABLE_FEATURE_INIT_SYSLOG) {
+               /* Log the message to syslogd */
+               if (where & L_LOG) {
+                       /* don't out "\r" */
+                       openlog(applet_name, 0, LOG_DAEMON);
+                       syslog(LOG_INFO, "init: %s", msg + 1);
+                       closelog();
+               }
+               msg[l++] = '\n';
+               msg[l] = '\0';
+       } else {
+               msg[l++] = '\n';
+               msg[l] = '\0';
+               /* Take full control of the log tty, and never close it.
+                * It's mine, all mine!  Muhahahaha! */
+               if (log_fd < 0) {
+                       if (!log_console) {
+                               log_fd = 2;
+                       } 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 == 2)
+                               return; /* don't print dup messages */
+               }
+       }
+
+       if (where & L_CONSOLE) {
+               /* Send console messages to console so people will see them. */
+               full_write(2, msg, l);
+       }
+}
+
+/* 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];
+       /* Paranoia (imagine 64bit kernel overwriting 32bit userspace stack) */
+       uint32_t bbox_reserved[16];
+};
+static void console_init(void)
+{
+       struct serial_struct sr;
+       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, 0);
+                       dup2(fd, 1);
+                       xmove_fd(fd, 2);
+               }
+               messageD(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(); - WRONG.
+                * It fails if "/dev/null" doesnt exist, and for init
+                * this is a real possibility! Open code it instead. */
+
+               int fd = open(bb_dev_null, O_RDWR);
+               if (fd < 0) {
+                       /* Give me _ANY_ open descriptor! */
+                       fd = xopen("/", O_RDONLY); /* we don't believe this can fail */
+               }
+               while ((unsigned)fd < 2)
+                       fd = dup(fd);
+               if (fd > 2)
+                       close(fd);
+       }
+
+       s = getenv("TERM");
+       if (ioctl(STDIN_FILENO, TIOCGSERIAL, &sr) == 0) {
+               /* Force the TERM setting to vt102 for serial console
+                * 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 dicipline 0 */
+       tty.c_line = 0;
+
+       /* Make it be sane */
+       tty.c_cflag &= CBAUD | CBAUDEX | CSIZE | CSTOPB | PARENB | PARODD;
+       tty.c_cflag |= CREAD | HUPCL | CLOCAL;
+
+       /* input modes */
+       tty.c_iflag = ICRNL | IXON | IXOFF;
+
+       /* output modes */
+       tty.c_oflag = OPOST | ONLCR;
+
+       /* local modes */
+       tty.c_lflag =
+               ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
+
+       tcsetattr(STDIN_FILENO, TCSANOW, &tty);
+}
+
+/* Open the new terminal device.
+ * NB: careful, we can be called after vfork! */
+static void open_stdio_to_tty(const char* tty_name, int exit_on_failure)
+{
+       /* empty tty_name means "use init's tty", else... */
+       if (tty_name[0]) {
+               int fd;
+               close(0);
+               /* 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));
+                       if (exit_on_failure)
+                               _exit(1);
+                       if (ENABLE_DEBUG_INIT)
+                               _exit(2);
+               /* NB: we don't reach this if we were called after vfork.
+                * Thus halt_reboot_pwoff() itself need not be vfork-safe. */
+                       halt_reboot_pwoff(SIGUSR1); /* halt the system */
+               }
+               dup2(0, 1);
+               dup2(0, 2);
+       }
+       set_sane_term();
+}
+
+/* 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;
+       sigset_t nmask, omask;
+
+       /* Block sigchild while forking (why?) */
+       sigemptyset(&nmask);
+       sigaddset(&nmask, SIGCHLD);
+       sigprocmask(SIG_BLOCK, &nmask, &omask);
+       if (BB_MMU && (a->action_type & ASKFIRST))
+               pid = fork();
+       else
+               pid = vfork();
+       sigprocmask(SIG_SETMASK, &omask, NULL);
+
+       if (pid < 0)
+               message(L_LOG | L_CONSOLE, "Can't fork");
+       if (pid)
+               return pid;
+
+       /* Child */
+
+       /* Reset signal handlers that were set by the parent process */
+       bb_signals(0
+               + (1 << SIGUSR1)
+               + (1 << SIGUSR2)
+               + (1 << SIGINT)
+               + (1 << SIGTERM)
+               + (1 << SIGHUP)
+               + (1 << SIGQUIT)
+               + (1 << SIGCONT)
+               + (1 << SIGSTOP)
+               + (1 << SIGTSTP)
+               , SIG_DFL);
+
+       /* Create a new session and make ourself the process
+        * group leader */
+       setsid();
+
+       /* Open the new terminal device */
+       open_stdio_to_tty(a->terminal, 1 /* - exit if open fails */);
+
+// NB: do not enable unless you change vfork to fork above
+#ifdef BUT_RUN_ACTIONS_ALREADY_DOES_WAITING
+       /* If the init Action requires us to wait, then force the
+        * supplied terminal to be the controlling tty. */
+       if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) {
+               /* Now fork off another process to just hang around */
+               pid = fork();
+               if (pid < 0) {
+                       message(L_LOG | L_CONSOLE, "Can't fork");
+                       _exit(1);
+               }
+
+               if (pid > 0) {
+                       /* Parent - wait till the child is done */
+                       bb_signals(0
+                               + (1 << SIGINT)
+                               + (1 << SIGTSTP)
+                               + (1 << SIGQUIT)
+                               , SIG_IGN);
+                       signal(SIGCHLD, SIG_DFL);
+
+                       waitfor(pid);
+                       /* See if stealing the controlling tty back is necessary */
+                       if (tcgetpgrp(0) != getpid())
+                               _exit(0);
+
+                       /* Use a temporary process to steal the controlling tty. */
+                       pid = fork();
+                       if (pid < 0) {
+                               message(L_LOG | L_CONSOLE, "Can't fork");
+                               _exit(1);
+                       }
+                       if (pid == 0) {
+                               setsid();
+                               ioctl(0, TIOCSCTTY, 1);
+                               _exit(0);
+                       }
+                       waitfor(pid);
+                       _exit(0);
+               }
+
+               /* Child - fall though to actually execute things */
+       }
+#endif
+
+       /* 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.
+                */
+               messageD(L_LOG, "waiting for enter to start '%s'"
+                                       "(pid %d, tty '%s')\n",
+                               a->command, getpid(), a->terminal);
+               full_write(1, press_enter, sizeof(press_enter) - 1);
+               while (safe_read(0, &c, 1) == 1 && c != '\n')
+                       continue;
+       }
+
+       if (ENABLE_FEATURE_INIT_COREDUMPS) {
+               struct stat sb;
+               if (stat(CORE_ENABLE_FLAG_FILE, &sb) == 0) {
+                       struct rlimit limit;
+                       limit.rlim_cur = RLIM_INFINITY;
+                       limit.rlim_max = RLIM_INFINITY;
+                       setrlimit(RLIMIT_CORE, &limit);
+               }
+       }
+
+       /* 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);
+}
+
+/* Run all commands of a particular type */
+static void run_actions(int action_type)
+{
+       struct init_action *a, *tmp;
+
+       for (a = init_action_list; a; a = tmp) {
+               tmp = a->next;
+               if (a->action_type & action_type) {
+                       // Pointless: run() will error out if open of device fails.
+                       ///* a->terminal of "" means "init's console" */
+                       //if (a->terminal[0] && access(a->terminal, R_OK | W_OK)) {
+                       //      //message(L_LOG | L_CONSOLE, "Device %s cannot be opened in RW mode", a->terminal /*, strerror(errno)*/);
+                       //      delete_init_action(a);
+                       //} else
+                       if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) {
+                               waitfor(run(a));
+                               delete_init_action(a);
+                       } else if (a->action_type & ONCE) {
+                               run(a);
+                               delete_init_action(a);
+                       } else if (a->action_type & (RESPAWN | ASKFIRST)) {
+                               /* Only run stuff with pid==0.  If they have
+                                * a pid, that means it is still running */
+                               if (a->pid == 0) {
+                                       a->pid = run(a);
+                               }
+                       }
+               }
+       }
+}
+
+static void init_reboot(unsigned long magic)
+{
+       pid_t pid;
+       /* We have to fork here, since the kernel calls do_exit(0) in
+        * linux/kernel/sys.c, which can cause the machine to panic when
+        * the init process is killed.... */
+       pid = vfork();
+       if (pid == 0) { /* child */
+               reboot(magic);
+               _exit(0);
+       }
+       waitfor(pid);
+}
+
+static void kill_all_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);
+
+       /* first disable all our signals */
+       sigprocmask_allsigs(SIG_BLOCK);
+
+       message(L_CONSOLE | L_LOG, "The system is going down NOW!");
+
+       /* Allow Ctrl-Alt-Del to reboot system. */
+       init_reboot(RB_ENABLE_CAD);
+
+       /* Send signals to every process _except_ pid 1 */
+       message(L_CONSOLE | L_LOG, "Sending SIG%s to all processes", "TERM");
+       kill(-1, SIGTERM);
+       sync();
+       sleep(1);
+
+       message(L_CONSOLE | L_LOG, "Sending SIG%s to all processes", "KILL");
+       kill(-1, SIGKILL);
+       sync();
+       sleep(1);
+}
+
+static void halt_reboot_pwoff(int sig)
+{
+       const char *m;
+       int rb;
+
+       kill_all_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 | L_LOG, "Requesting system %s", m);
+       /* allow time for last message to reach serial console */
+       sleep(2);
+       init_reboot(rb);
+       loop_forever();
+}
+
+/* Handler for QUIT - exec "restart" action,
+ * else (no such action defined) do nothing */
+static void exec_restart_action(int sig ATTRIBUTE_UNUSED)
+{
+       struct init_action *a;
+
+       for (a = init_action_list; a; a = a->next) {
+               if (a->action_type & RESTART) {
+                       kill_all_processes();
+
+                       /* unblock all signals (blocked in kill_all_processes()) */
+                       sigprocmask_allsigs(SIG_UNBLOCK);
+
+                       /* Open the new terminal device */
+                       open_stdio_to_tty(a->terminal, 0 /* - halt if open fails */);
+
+                       messageD(L_CONSOLE | L_LOG, "Trying to re-exec %s", a->command);
+                       init_exec(a->command);
+                       sleep(2);
+                       init_reboot(RB_HALT_SYSTEM);
+                       loop_forever();
+               }
+       }
+}
+
+static void ctrlaltdel_signal(int sig ATTRIBUTE_UNUSED)
+{
+       run_actions(CTRLALTDEL);
+}
+
+/* The SIGSTOP & SIGTSTP handler */
+static void stop_handler(int sig ATTRIBUTE_UNUSED)
+{
+       int saved_errno = errno;
+
+       got_cont = 0;
+       while (!got_cont)
+               pause();
+
+       errno = saved_errno;
+}
+
+/* The SIGCONT handler */
+static void cont_handler(int sig ATTRIBUTE_UNUSED)
+{
+       got_cont = 1;
+}
+
+static void new_init_action(uint8_t action_type, const char *command, const char *cons)
+{
+       struct init_action *a, *last;
+
+// Why?
+//     if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST))
+//             return;
+
+       /* Append to the end of the list */
+       for (a = last = init_action_list; a; a = a->next) {
+               /* don't enter action if it's already in the list,
+                * but do overwrite existing actions */
+               if ((strcmp(a->command, command) == 0)
+                && (strcmp(a->terminal, cons) == 0)
+               ) {
+                       a->action_type = action_type;
+                       return;
+               }
+               last = a;
+       }
+
+       a = xzalloc(sizeof(*a));
+       if (last) {
+               last->next = a;
+       } else {
+               init_action_list = a;
+       }
+       a->action_type = action_type;
+       safe_strncpy(a->command, command, sizeof(a->command));
+       safe_strncpy(a->terminal, cons, sizeof(a->terminal));
+       messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
+               a->command, a->action_type, a->terminal);
+}
+
+static void delete_init_action(struct init_action *action)
+{
+       struct init_action *a, *b = NULL;
+
+       for (a = init_action_list; a; b = a, a = a->next) {
+               if (a == action) {
+                       if (b == NULL) {
+                               init_action_list = a->next;
+                       } else {
+                               b->next = a->next;
+                       }
+                       free(a);
+                       break;
+               }
+       }
+}
+
+/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
+ * then parse_inittab() simply adds in some default
+ * actions(i.e., runs INIT_SCRIPT and then starts a pair
+ * of "askfirst" shells).  If CONFIG_FEATURE_USE_INITTAB
+ * _is_ defined, but /etc/inittab is missing, this
+ * results in the same set of default behaviors.
+ */
+static void parse_inittab(void)
+{
+       FILE *file;
+       char buf[COMMAND_SIZE];
+
+       if (ENABLE_FEATURE_USE_INITTAB)
+               file = fopen(INITTAB, "r");
+       else
+               file = NULL;
+
+       /* No inittab file -- set up some default behavior */
+       if (file == NULL) {
+               /* 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, "");
+               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;
+       }
+
+       while (fgets(buf, COMMAND_SIZE, file) != NULL) {
+               static const char actions[] =
+                       STR_SYSINIT    "sysinit\0"
+                       STR_RESPAWN    "respawn\0"
+                       STR_ASKFIRST   "askfirst\0"
+                       STR_WAIT       "wait\0"
+                       STR_ONCE       "once\0"
+                       STR_CTRLALTDEL "ctrlaltdel\0"
+                       STR_SHUTDOWN   "shutdown\0"
+                       STR_RESTART    "restart\0"
+               ;
+               char tmpConsole[CONSOLE_NAME_SIZE];
+               char *id, *runlev, *action, *command;
+               const char *a;
+
+               /* Skip leading spaces */
+               id = skip_whitespace(buf);
+               /* Trim the trailing '\n' */
+               *strchrnul(id, '\n') = '\0';
+               /* Skip the line if it is a comment */
+               if (*id == '#' || *id == '\0')
+                       continue;
+
+               /* Line is: "id:runlevel_ignored:action:command" */
+               runlev = strchr(id, ':');
+               if (runlev == NULL /*|| runlev[1] == '\0' - not needed */)
+                       goto bad_entry;
+               action = strchr(runlev + 1, ':');
+               if (action == NULL /*|| action[1] == '\0' - not needed */)
+                       goto bad_entry;
+               command = strchr(action + 1, ':');
+               if (command == NULL || command[1] == '\0')
+                       goto bad_entry;
+
+               *command = '\0'; /* action => ":action\0" now */
+               for (a = actions; a[0]; a += strlen(a) + 1) {
+                       if (strcmp(a + 1, action + 1) == 0) {
+                               *runlev = '\0';
+                               if (*id != '\0') {
+                                       if (strncmp(id, "/dev/", 5) == 0)
+                                               id += 5;
+                                       strcpy(tmpConsole, "/dev/");
+                                       safe_strncpy(tmpConsole + 5, id,
+                                               sizeof(tmpConsole) - 5);
+                                       id = tmpConsole;
+                               }
+                               new_init_action((uint8_t)a[0], command + 1, id);
+                               goto next_line;
+                       }
+               }
+               *command = ':';
+               /* Choke on an unknown action */
+ bad_entry:
+               message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", id);
+ next_line: ;
+       }
+       fclose(file);
+}
+
+#if ENABLE_FEATURE_USE_INITTAB
+static void reload_signal(int sig ATTRIBUTE_UNUSED)
+{
+       struct init_action *a, *tmp;
+
+       message(L_LOG, "reloading /etc/inittab");
+
+       /* disable old entrys */
+       for (a = init_action_list; a; a = a->next) {
+               a->action_type = ONCE;
+       }
+
+       parse_inittab();
+
+       if (ENABLE_FEATURE_KILL_REMOVED) {
+               /* Be nice and send SIGTERM first */
+               for (a = init_action_list; a; a = a->next) {
+                       pid_t pid = a->pid;
+                       if ((a->action_type & ONCE) && pid != 0) {
+                               kill(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) {
+                               pid_t pid = a->pid;
+                               if ((a->action_type & ONCE) && pid != 0) {
+                                       kill(pid, SIGKILL);
+                               }
+                       }
+                       _exit(0);
+               }
+#endif
+       }
+
+       /* remove unused entrys */
+       for (a = init_action_list; a; a = tmp) {
+               tmp = a->next;
+               if ((a->action_type & (ONCE | SYSINIT | WAIT)) && a->pid == 0) {
+                       delete_init_action(a);
+               }
+       }
+       run_actions(RESPAWN | ASKFIRST);
+}
+#endif
+
+int init_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int init_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       struct init_action *a;
+       pid_t wpid;
+
+       die_sleep = 30 * 24*60*60; /* if xmalloc will ever die... */
+
+       if (argv[1] && !strcmp(argv[1], "-q")) {
+               return kill(1, SIGHUP);
+       }
+
+       if (!ENABLE_DEBUG_INIT) {
+               /* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
+               if (getpid() != 1
+                && (!ENABLE_FEATURE_INITRD || !strstr(applet_name, "linuxrc"))
+               ) {
+                       bb_show_usage();
+               }
+               /* Set up sig handlers  -- be sure to
+                * clear all of these in run() */
+               signal(SIGQUIT, exec_restart_action);
+               bb_signals(0
+                       + (1 << SIGUSR1)  /* halt */
+                       + (1 << SIGUSR2)  /* poweroff */
+                       + (1 << SIGTERM)  /* reboot */
+                       , halt_reboot_pwoff);
+               signal(SIGINT, ctrlaltdel_signal);
+               signal(SIGCONT, cont_handler);
+               bb_signals(0
+                       + (1 << SIGSTOP)
+                       + (1 << SIGTSTP)
+                       , stop_handler);
+
+               /* Turn off rebooting via CTL-ALT-DEL -- we get a
+                * SIGINT on CAD so we can shut things down gracefully... */
+               init_reboot(RB_DISABLE_CAD);
+       }
+
+       /* Figure out where the default console should be */
+       console_init();
+       set_sane_term();
+       chdir("/");
+       setsid();
+       {
+               const char *const *e;
+               /* Make sure environs is set to something sane */
+               for (e = environment; *e; e++)
+                       putenv((char *) *e);
+       }
+
+       if (argv[1]) setenv("RUNLEVEL", argv[1], 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) &&
+                       (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'))
+       ) {
+               /* Start a shell on console */
+               new_init_action(RESPAWN, bb_default_login_shell, "");
+       } else {
+               /* Not in single user mode -- see what inittab says */
+
+               /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
+                * then parse_inittab() simply adds in some default
+                * actions(i.e., runs INIT_SCRIPT and then starts a pair
+                * of "askfirst" shells */
+               parse_inittab();
+       }
+
+#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(1);
+               }
+       }
+#endif /* CONFIG_SELINUX */
+
+       /* 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));
+
+       /* Now run everything that needs to be run */
+
+       /* First run the sysinit command */
+       run_actions(SYSINIT);
+
+       /* Next run anything that wants to block */
+       run_actions(WAIT);
+
+       /* Next run anything to be run only once */
+       run_actions(ONCE);
+
+       /* Redefine SIGHUP to reread /etc/inittab */
+#if ENABLE_FEATURE_USE_INITTAB
+       signal(SIGHUP, reload_signal);
+#else
+       signal(SIGHUP, SIG_IGN);
+#endif
+
+       /* Now run the looping stuff for the rest of forever */
+       while (1) {
+               /* run the respawn/askfirst stuff */
+               run_actions(RESPAWN | ASKFIRST);
+
+               /* Don't consume all CPU time -- sleep a bit */
+               sleep(1);
+
+               /* Wait for any child process to exit */
+               wpid = wait(NULL);
+               while (wpid > 0) {
+                       /* Find out who died and clean up their corpse */
+                       for (a = init_action_list; a; a = a->next) {
+                               if (a->pid == wpid) {
+                                       /* Set the pid to 0 so that the process gets
+                                        * restarted by run_actions() */
+                                       a->pid = 0;
+                                       message(L_LOG, "process '%s' (pid %d) exited. "
+                                                       "Scheduling for restart.",
+                                                       a->command, wpid);
+                               }
+                       }
+                       /* see if anyone else is waiting to be reaped */
+                       wpid = wait_any_nohang(NULL);
+               }
+       }
+}
diff --git a/init/mesg.c b/init/mesg.c
new file mode 100644 (file)
index 0000000..cfb517f
--- /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 = 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..842dd1f
--- /dev/null
@@ -0,0 +1,147 @@
+#
+# 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.
+endmenu
diff --git a/libbb/Kbuild b/libbb/Kbuild
new file mode 100644 (file)
index 0000000..5740d92
--- /dev/null
@@ -0,0 +1,140 @@
+# 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_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
+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
+lib-y += messages.o
+lib-y += mode_string.o
+lib-y += mtab_file.o
+lib-y += obscure.o
+lib-y += parse_mode.o
+lib-y += perror_msg.o
+lib-y += perror_msg_and_die.o
+lib-y += perror_nomsg.o
+lib-y += perror_nomsg_and_die.o
+lib-y += pidfile.o
+lib-y += printable.o
+lib-y += process_escape_sequence.o
+lib-y += procps.o
+lib-y += ptr_to_globals.o
+lib-y += read.o
+lib-y += recursive_action.o
+lib-y += remove_file.o
+lib-y += restricted_shell.o
+lib-y += run_shell.o
+lib-y += safe_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 += 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 += xatonum.o
+lib-y += xconnect.o
+lib-y += xfuncs.o
+lib-y += xgetcwd.o
+lib-y += xgethostbyname.o
+lib-y += xreadlink.o
+
+# conditionally compiled objects:
+lib-$(CONFIG_FEATURE_MOUNT_LOOP) += loop.o
+lib-$(CONFIG_LOSETUP) += loop.o
+lib-$(CONFIG_FEATURE_MTAB_SUPPORT) += mtab.o
+lib-$(CONFIG_PASSWD) += pw_encrypt.o crypt_make_salt.o update_passwd.o
+lib-$(CONFIG_CHPASSWD) += pw_encrypt.o crypt_make_salt.o update_passwd.o
+lib-$(CONFIG_CRYPTPW) += pw_encrypt.o crypt_make_salt.o
+lib-$(CONFIG_SULOGIN) += pw_encrypt.o
+lib-$(CONFIG_FEATURE_HTTPD_AUTH_MD5) += pw_encrypt.o
+lib-$(CONFIG_VLOCK) += correct_password.o
+lib-$(CONFIG_SU) += correct_password.o
+lib-$(CONFIG_LOGIN) += correct_password.o
+lib-$(CONFIG_DF) += find_mount_point.o
+lib-$(CONFIG_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..e2bb378
--- /dev/null
@@ -0,0 +1,688 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) tons of folks.  Tracking down who wrote what
+ * isn't something I'm going to worry about...  If you wrote something
+ * here, please feel free to acknowledge your work.
+ *
+ * Based in part on code from sash, Copyright (c) 1999 by David I. Bell
+ * Permission has been granted to redistribute this code under the GPL.
+ *
+ * Licensed under GPLv2 or later, see file License in this tarball for details.
+ */
+
+#include <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"
+
+
+#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,
+                       /* inbuf:  */ packed_usage,
+                       /* 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 */
+
+
+void bb_show_usage(void)
+{
+       if (ENABLE_SHOW_USAGE) {
+               const char *format_string;
+               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--;
+               }
+
+               fprintf(stderr, "%s multi-call binary\n", bb_banner);
+               format_string = "\nUsage: %s %s\n\n";
+               if (*p == '\b')
+                       format_string = "\nNo help available.\n\n";
+               fprintf(stderr, format_string, applet_name, p);
+               dealloc_usage_messages((char*)usage_string);
+       }
+       xfunc_die();
+}
+
+
+/* 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));
+}
+int find_applet_by_name(const char *name)
+{
+       /* 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;
+}
+
+
+#ifdef __GLIBC__
+/* Make it reside in R/W memory: */
+int *const bb_errno __attribute__ ((section (".data")));
+#endif
+
+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] && strcmp(argv[1], "--help") == 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
+
+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(config_file, "r"))      /* 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;
+       int 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;
+               int 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 -= sizeof("start-stop-daemon, ") + 8;
+
+               printf("%s multi-call binary\n", bb_banner); /* reuse const string... */
+               printf("Copyright (C) 1998-2007 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) {
+                       if (col > output_width) {
+                               puts(",");
+                               col = 0;
+                       }
+                       col += printf("%s%s", (col ? ", " : "\t"), a);
+                       a += strlen(a) + 1;
+               }
+               puts("\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");
+}
+
+void 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"))
+               bb_show_usage();
+       if (ENABLE_FEATURE_SUID)
+               check_suid(applet_no);
+       exit(applet_main[applet_no](argc, argv));
+}
+
+void 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));
+}
+
+
+#if ENABLE_BUILD_LIBBUSYBOX
+int lbb_main(char **argv)
+#else
+int main(int argc ATTRIBUTE_UNUSED, char **argv)
+#endif
+{
+       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");
+}
diff --git a/libbb/ask_confirmation.c b/libbb/ask_confirmation.c
new file mode 100644 (file)
index 0000000..646ec4b
--- /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 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..3ad0e97
--- /dev/null
@@ -0,0 +1,77 @@
+/* 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 <termios.h>
+
+#include "libbb.h"
+
+/* do nothing signal handler */
+static void askpass_timeout(int ATTRIBUTE_UNUSED ignore)
+{
+}
+
+char *bb_askpass(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(STDIN_FILENO, &oldtio);
+       tcflush(STDIN_FILENO, TCIFLUSH);
+       tio = oldtio;
+       tio.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
+       tio.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP);
+       tcsetattr(STDIN_FILENO, 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(STDIN_FILENO, passwd, sizeof_passwd - 1) > 0) {
+               ret = passwd;
+               i = 0;
+               /* Last byte is guaranteed to be 0
+                  (read did not overwrite it) */
+               do {
+                       if (passwd[i] == '\r' || passwd[i] == '\n')
+                               passwd[i] = '\0';
+               } while (passwd[i++]);
+       }
+
+       if (timeout) {
+               alarm(0);
+       }
+       sigaction_set(SIGINT, &oldsa);
+
+       tcsetattr(STDIN_FILENO, 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..e6832f8
--- /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 *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..aa26ade
--- /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 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..2bdb662
--- /dev/null
@@ -0,0 +1,99 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * password 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"
+
+#define assert(x) ((void)0)
+
+/* internal function for bb_getpwuid and bb_getgrgid */
+/* Hacked by Tito Ragusa (c) 2004 <farmatito@tiscali.it> to make it more
+ * flexible:
+ *
+ * bufsize > 0:      If idname is not NULL it is copied to buffer,
+ *                   and buffer is returned. Else id as string is written
+ *                   to buffer, and NULL is returned.
+ *
+ * bufsize == 0:     idname is returned.
+ *
+ * bufsize < 0:      If idname is not NULL it is returned.
+ *                   Else an error message is printed and the program exits.
+ */
+static char* bb_getug(char *buffer, int bufsize, char *idname, long id, char prefix)
+{
+       if (bufsize > 0) {
+               assert(buffer != NULL);
+               if (idname) {
+                       return safe_strncpy(buffer, idname, bufsize);
+               }
+               snprintf(buffer, bufsize, "%ld", id);
+       } else if (bufsize < 0 && !idname) {
+               bb_error_msg_and_die("unknown %cid %ld", prefix, id);
+       }
+       return idname;
+}
+
+/* bb_getpwuid, bb_getgrgid:
+ * bb_getXXXid(buf, bufsz, id) - copy user/group name or id
+ *               as a string to buf, return user/group name or NULL
+ * bb_getXXXid(NULL, 0, id) - return user/group name or NULL
+ * bb_getXXXid(NULL, -1, id) - return user/group name or exit
+ */
+/* gets a username given a uid */
+char* bb_getpwuid(char *name, int bufsize, long uid)
+{
+       struct passwd *myuser = getpwuid(uid);
+
+       return bb_getug(name, bufsize,
+                       (myuser ? myuser->pw_name : (char*)myuser),
+                       uid, 'u');
+}
+/* gets a groupname given a gid */
+char* bb_getgrgid(char *group, int bufsize, long gid)
+{
+       struct group *mygroup = getgrgid(gid);
+
+       return bb_getug(group, bufsize,
+                       (mygroup ? mygroup->gr_name : (char*)mygroup),
+                       gid, 'g');
+}
+
+/* returns a gid given a group name */
+long xgroup2gid(const char *name)
+{
+       struct group *mygroup;
+
+       mygroup = getgrnam(name);
+       if (mygroup == NULL)
+               bb_error_msg_and_die("unknown group name: %s", name);
+
+       return mygroup->gr_gid;
+}
+
+/* returns a uid given a username */
+long xuname2uid(const char *name)
+{
+       struct passwd *myuser;
+
+       myuser = getpwnam(name);
+       if (myuser == NULL)
+               bb_error_msg_and_die("unknown user name: %s", name);
+
+       return myuser->pw_uid;
+}
+
+unsigned long get_ug_id(const char *s,
+               long (*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..e8673ab
--- /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 bb_pstrcmp(const void *a, const void *b)
+{
+       return strcmp(*(char**)a, *(char**)b);
+}
+
+void qsort_string_vector(char **sv, unsigned count)
+{
+       qsort(sv, count, sizeof(char*), bb_pstrcmp);
+}
diff --git a/libbb/bb_strtonum.c b/libbb/bb_strtonum.c
new file mode 100644 (file)
index 0000000..50ef0ba
--- /dev/null
@@ -0,0 +1,156 @@
+/* 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 othervise ok
+ *    Return value is still valid, caller should just check whether end[0]
+ *    is a valid terminating char for particular case. OTOH, if caller
+ *    requires '\0' terminated input, [s]he can just check errno == 0.
+ * errno = ERANGE if value had alphanumeric terminating char ("1234abcg").
+ * errno = ERANGE if value is out of range, missing, etc.
+ * errno = ERANGE if value had minus sign for strtouXX (even "-0" is not ok )
+ *    return value is all-ones in this case.
+ */
+
+static unsigned long long ret_ERANGE(void)
+{
+       errno = ERANGE; /* this ain't as small as it looks (on glibc) */
+       return ULLONG_MAX;
+}
+
+static unsigned long long handle_errors(unsigned long long v, char **endp, char *endptr)
+{
+       if (endp) *endp = endptr;
+
+       /* Check for the weird "feature":
+        * a "-" string is apparently a valid "number" for strto[u]l[l]!
+        * It returns zero and errno is 0! :( */
+       if (endptr[-1] == '-')
+               return ret_ERANGE();
+
+       /* errno is already set to ERANGE by strtoXXX if value overflowed */
+       if (endptr[0]) {
+               /* "1234abcg" or out-of-range? */
+               if (isalnum(endptr[0]) || errno)
+                       return ret_ERANGE();
+               /* good number, just suspicious terminator */
+               errno = EINVAL;
+       }
+       return v;
+}
+
+
+unsigned long long bb_strtoull(const char *arg, char **endp, int base)
+{
+       unsigned long long v;
+       char *endptr;
+
+       /* strtoul("  -4200000000") returns 94967296, errno 0 (!) */
+       /* I don't think that this is right. Preventing this... */
+       if (!isalnum(arg[0])) return ret_ERANGE();
+
+       /* not 100% correct for lib func, but convenient for the caller */
+       errno = 0;
+       v = strtoull(arg, &endptr, base);
+       return handle_errors(v, endp, endptr);
+}
+
+long long bb_strtoll(const char *arg, char **endp, int base)
+{
+       unsigned long long v;
+       char *endptr;
+
+       if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE();
+       errno = 0;
+       v = strtoll(arg, &endptr, base);
+       return handle_errors(v, endp, endptr);
+}
+
+#if ULONG_MAX != ULLONG_MAX
+unsigned long bb_strtoul(const char *arg, char **endp, int base)
+{
+       unsigned long v;
+       char *endptr;
+
+       if (!isalnum(arg[0])) return ret_ERANGE();
+       errno = 0;
+       v = strtoul(arg, &endptr, base);
+       return handle_errors(v, endp, endptr);
+}
+
+long bb_strtol(const char *arg, char **endp, int base)
+{
+       long v;
+       char *endptr;
+
+       if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE();
+       errno = 0;
+       v = strtol(arg, &endptr, base);
+       return handle_errors(v, endp, endptr);
+}
+#endif
+
+#if UINT_MAX != ULONG_MAX
+unsigned bb_strtou(const char *arg, char **endp, int base)
+{
+       unsigned long v;
+       char *endptr;
+
+       if (!isalnum(arg[0])) return ret_ERANGE();
+       errno = 0;
+       v = strtoul(arg, &endptr, base);
+       if (v > UINT_MAX) return ret_ERANGE();
+       return handle_errors(v, endp, endptr);
+}
+
+int bb_strtoi(const char *arg, char **endp, int base)
+{
+       long v;
+       char *endptr;
+
+       if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE();
+       errno = 0;
+       v = strtol(arg, &endptr, base);
+       if (v > INT_MAX) return ret_ERANGE();
+       if (v < INT_MIN) return ret_ERANGE();
+       return handle_errors(v, endp, endptr);
+}
+#endif
+
+/* Floating point */
+
+#if 0
+
+#include <math.h>  /* just for HUGE_VAL */
+#define NOT_DIGIT(a) (((unsigned char)(a-'0')) > 9)
+double bb_strtod(const char *arg, char **endp)
+{
+       double v;
+       char *endptr;
+
+       if (arg[0] != '-' && NOT_DIGIT(arg[0])) goto err;
+       errno = 0;
+       v = strtod(arg, &endptr);
+       if (endp) *endp = endptr;
+       if (endptr[0]) {
+               /* "1234abcg" or out-of-range? */
+               if (isalnum(endptr[0]) || errno) {
+ err:
+                       errno = ERANGE;
+                       return HUGE_VAL;
+               }
+               /* good number, just suspicious terminator */
+               errno = EINVAL;
+       }
+       return v;
+}
+
+#endif
diff --git a/libbb/change_identity.c b/libbb/change_identity.c
new file mode 100644 (file)
index 0000000..da840bf
--- /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 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..8ffaff5
--- /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 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..151b508
--- /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 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 index_in_strings(const char *strings, const char *key)
+{
+       int idx = 0;
+
+       while (strings[0]) {
+               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 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 index_in_substrings(const char *strings, const char *key)
+{
+       int len = strlen(key);
+
+       if (len) {
+               int idx = 0;
+               while (strings[0]) {
+                       if (strncmp(strings, key, len) == 0) {
+                               return idx;
+                       }
+                       strings += strlen(strings) + 1; /* skip NUL */
+                       idx++;
+               }
+       }
+       return -1;
+}
+
+const char *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..9aae601
--- /dev/null
@@ -0,0 +1,27 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) many different people.
+ * If you wrote this, please acknowledge your work.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* concatenate path and file name to new allocation buffer,
+ * not adding '/' if path name already has '/'
+*/
+
+#include "libbb.h"
+
+char *concat_path_file(const char *path, const char *filename)
+{
+       char *lc;
+
+       if (!path)
+               path = "";
+       lc = last_char_is(path, '/');
+       while (*filename == '/')
+               filename++;
+       return xasprintf("%s%s%s", path, (lc==NULL ? "/" : ""), filename);
+}
diff --git a/libbb/concat_subpath_file.c b/libbb/concat_subpath_file.c
new file mode 100644 (file)
index 0000000..1c00588
--- /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 *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..d37d515
--- /dev/null
@@ -0,0 +1,378 @@
+/* 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 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;
+
+#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 (S_ISREG(source_stat.st_mode)
+        /* DEREF uses stat, which never returns S_ISLNK() == true. */
+        /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */
+       ) {
+               int src_fd;
+               int dst_fd;
+
+               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;
+
+               /* 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, source_stat.st_mode);
+               } else  /* safe way: */
+                       dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_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, source_stat.st_mode);
+                       if (dst_fd < 0) {
+                               close(src_fd);
+                               return -1;
+                       }
+               }
+
+#if ENABLE_SELINUX
+               if (((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT)
+                   || (flags & 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);
+               goto preserve_mode_ugid_time;
+       }
+
+       /* 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..08bc6f8
--- /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 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 bb_copyfd_size(int fd1, int fd2, off_t size)
+{
+       if (size) {
+               return bb_full_fd_action(fd1, fd2, size);
+       }
+       return 0;
+}
+
+void 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 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..96bb10e
--- /dev/null
@@ -0,0 +1,77 @@
+/* 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 correct_password(const struct passwd *pw)
+{
+       char *unencrypted, *encrypted;
+       const char *correct;
+#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;
+               int 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_askpass(0, "Password: ");
+       if (!unencrypted) {
+               return 0;
+       }
+       encrypted = crypt(unencrypted, correct);
+       memset(unencrypted, 0, strlen(unencrypted));
+       return strcmp(encrypted, correct) == 0;
+}
diff --git a/libbb/crc32.c b/libbb/crc32.c
new file mode 100644 (file)
index 0000000..acbc458
--- /dev/null
@@ -0,0 +1,40 @@
+/* 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
+ */
+
+#include "libbb.h"
+
+uint32_t *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..a22ac5d
--- /dev/null
@@ -0,0 +1,36 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * create raw socket for icmp (IPv6 version) protocol
+ * and drop root privileges if running setuid
+ */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_IPV6
+int 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..64beba8
--- /dev/null
@@ -0,0 +1,34 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * create raw socket for icmp protocol
+ * and drop root privileges if running setuid
+ */
+
+#include "libbb.h"
+
+int 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/crypt_make_salt.c b/libbb/crypt_make_salt.c
new file mode 100644 (file)
index 0000000..ebdf024
--- /dev/null
@@ -0,0 +1,45 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * crypt_make_salt
+ *
+ * i64c was also put here, this is the only function that uses it.
+ *
+ * Lifted from loginutils/passwd.c by Thomas Lundquist <thomasez@zelow.no>
+ *
+ */
+
+#include "libbb.h"
+
+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 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;
+}
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..6907e98
--- /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 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..337ac60
--- /dev/null
@@ -0,0 +1,36 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Ckeck 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 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..4d6472e
--- /dev/null
@@ -0,0 +1,811 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Support code for the hexdump and od applets,
+ * based on code from util-linux v 2.11l
+ *
+ * Copyright (c) 1989
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Original copyright notice is retained at the end of this file.
+ */
+
+#include "libbb.h"
+#include "dump.h"
+
+enum _vflag bb_dump_vflag = FIRST;
+FS *bb_dump_fshead;                            /* head of format strings */
+static FU *endfu;
+static char **_argv;
+static off_t savaddress;       /* saved address/offset in stream */
+static off_t eaddress; /* end address */
+static off_t address;  /* address/offset in stream */
+off_t bb_dump_skip;                            /* bytes to skip */
+static int exitval;                    /* final exit value */
+int bb_dump_blocksize;                 /* data block size */
+int bb_dump_length = -1;               /* max bytes to read */
+
+static const char index_str[] 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";
+
+int bb_dump_size(FS * fs)
+{
+       FU *fu;
+       int bcnt, cur_size;
+       char *fmt;
+       const char *p;
+       int prec;
+
+       /* figure out the data block bb_dump_size needed for each format unit */
+       for (cur_size = 0, fu = fs->nextfu; fu; fu = fu->nextfu) {
+               if (fu->bcnt) {
+                       cur_size += fu->bcnt * fu->reps;
+                       continue;
+               }
+               for (bcnt = prec = 0, fmt = fu->fmt; *fmt; ++fmt) {
+                       if (*fmt != '%')
+                               continue;
+                       /*
+                        * bb_dump_skip any special chars -- save precision in
+                        * case it's a %s format.
+                        */
+                       while (strchr(index_str + 1, *++fmt));
+                       if (*fmt == '.' && isdigit(*++fmt)) {
+                               prec = atoi(fmt);
+                               while (isdigit(*++fmt));
+                       }
+                       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(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.
+                        */
+
+                       /* bb_dump_skip preceding text and up to the next % sign */
+                       for (p1 = fmtp; *p1 && *p1 != '%'; ++p1);
+
+                       /* only text in the string */
+                       if (!*p1) {
+                               pr->fmt = fmtp;
+                               pr->flags = F_TEXT;
+                               break;
+                       }
+
+                       /*
+                        * get precision for %s -- if have a byte count, don't
+                        * need it.
+                        */
+                       if (fu->bcnt) {
+                               sokay = USEBCNT;
+                               /* bb_dump_skip to conversion character */
+                               for (++p1; strchr(index_str, *p1); ++p1);
+                       } else {
+                               /* bb_dump_skip any special chars, field width */
+                               while (strchr(index_str + 1, *++p1));
+                               if (*p1 == '.' && isdigit(*++p1)) {
+                                       sokay = USEPREC;
+                                       prec = atoi(p1);
+                                       while (isdigit(*++p1));
+                               } else
+                                       sokay = NOTOKAY;
+                       }
+
+                       p2 = p1 + 1;    /* set end pointer */
+
+                       /*
+                        * figure out the byte count for each conversion;
+                        * rewrite the format as necessary, set up blank-
+                        * pbb_dump_adding for end of data.
+                        */
+
+                       if (*p1 == 'c') {
+                               pr->flags = F_CHAR;
+                       DO_BYTE_COUNT_1:
+                               byte_count_str = "\001";
+                       DO_BYTE_COUNT:
+                               if (fu->bcnt) {
+                                       do {
+                                               if (fu->bcnt == *byte_count_str) {
+                                                       break;
+                                               }
+                                       } while (*++byte_count_str);
+                               }
+                               /* Unlike the original, output the remainder of the format string. */
+                               if (!*byte_count_str) {
+                                       bb_error_msg_and_die("bad byte count for conversion character %s", p1);
+                               }
+                               pr->bcnt = *byte_count_str;
+                       } else if (*p1 == 'l') {
+                               ++p2;
+                               ++p1;
+                       DO_INT_CONV:
+                               {
+                                       const char *e;
+                                       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':
+                                       endfu = fu;
+                                       fu->flags |= F_IGNORE;
+                                       /* FALLTHROUGH */
+                               case 'a':
+                                       pr->flags = F_ADDRESS;
+                                       ++p2;
+                                       if ((p1[2] != 'd') && (p1[2] != 'o') && (p1[2] != 'x')) {
+                                               goto DO_BAD_CONV_CHAR;
+                                       }
+                                       *p1 = p1[2];
+                                       break;
+                               case 'c':
+                                       pr->flags = F_C;
+                                       /* *p1 = 'c';   set in conv_c */
+                                       goto DO_BYTE_COUNT_1;
+                               case 'p':
+                                       pr->flags = F_P;
+                                       *p1 = 'c';
+                                       goto DO_BYTE_COUNT_1;
+                               case 'u':
+                                       pr->flags = F_U;
+                                       /* *p1 = 'c';   set in conv_u */
+                                       goto DO_BYTE_COUNT_1;
+                               default:
+                                       goto DO_BAD_CONV_CHAR;
+                               }
+                       } else {
+                       DO_BAD_CONV_CHAR:
+                               bb_error_msg_and_die("bad conversion character %%%s", p1);
+                       }
+
+                       /*
+                        * copy to PR format string, set conversion character
+                        * pointer, update original.
+                        */
+                       savech = *p2;
+                       p1[1] = '\0';
+                       pr->fmt = xstrdup(fmtp);
+                       *p2 = savech;
+                       pr->cchar = pr->fmt + (p1 - fmtp);
+
+                       /* DBU:[dave@cray.com] w/o this, trailing fmt text, space is lost.
+                        * Skip subsequent text and up to the next % sign and tack the
+                        * additional text onto fmt: eg. if fmt is "%x is a HEX number",
+                        * we lose the " is a HEX number" part of fmt.
+                        */
+                       for (p3 = p2; *p3 && *p3 != '%'; p3++);
+                       if (p3 > p2)
+                       {
+                               savech = *p3;
+                               *p3 = '\0';
+                               pr->fmt = xrealloc(pr->fmt, strlen(pr->fmt)+(p3-p2)+1);
+                               strcat(pr->fmt, p2);
+                               *p3 = savech;
+                               p2 = p3;
+                       }
+
+                       fmtp = p2;
+
+                       /* only one conversion character if byte count */
+                       if (!(pr->flags & F_ADDRESS) && fu->bcnt && nconv++) {
+                               bb_error_msg_and_die("byte count with multiple conversion characters");
+                       }
+               }
+               /*
+                * if format unit byte count not specified, figure it out
+                * so can adjust rep count later.
+                */
+               if (!fu->bcnt)
+                       for (pr = fu->nextpr; pr; pr = pr->nextpr)
+                               fu->bcnt += pr->bcnt;
+       }
+       /*
+        * if the format string interprets any data at all, and it's
+        * not the same as the bb_dump_blocksize, and its last format unit
+        * interprets any data at all, and has no iteration count,
+        * repeat it as necessary.
+        *
+        * if, rep count is greater than 1, no trailing whitespace
+        * gets output from the last iteration of the format unit.
+        */
+       for (fu = fs->nextfu;; fu = fu->nextfu) {
+               if (!fu->nextfu && fs->bcnt < bb_dump_blocksize &&
+                       !(fu->flags & F_SETREP) && fu->bcnt)
+                       fu->reps += (bb_dump_blocksize - fs->bcnt) / fu->bcnt;
+               if (fu->reps > 1) {
+                       for (pr = fu->nextpr;; pr = pr->nextpr)
+                               if (!pr->nextpr)
+                                       break;
+                       for (p1 = pr->fmt, p2 = NULL; *p1; ++p1)
+                               p2 = isspace(*p1) ? p1 : NULL;
+                       if (p2)
+                               pr->nospace = p2;
+               }
+               if (!fu->nextfu)
+                       break;
+       }
+}
+
+static void do_skip(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))) && bb_dump_skip >= sbuf.st_size) {
+                       /* If bb_dump_size valid and bb_dump_skip >= size */
+                       bb_dump_skip -= sbuf.st_size;
+                       address += sbuf.st_size;
+                       return;
+               }
+       }
+       if (fseek(stdin, bb_dump_skip, SEEK_SET)) {
+               bb_simple_perror_msg_and_die(fname);
+       }
+       savaddress = address += bb_dump_skip;
+       bb_dump_skip = 0;
+}
+
+static int next(char **argv)
+{
+       static smallint done;
+
+       int statok;
+
+       if (argv) {
+               _argv = argv;
+               return 1;
+       }
+       for (;;) {
+               if (*_argv) {
+                       if (!(freopen(*_argv, "r", stdin))) {
+                               bb_simple_perror_msg(*_argv);
+                               exitval = 1;
+                               ++_argv;
+                               continue;
+                       }
+                       done = statok = 1;
+               } else {
+                       if (done)
+                               return 0;
+                       done = 1;
+                       statok = 0;
+               }
+               if (bb_dump_skip)
+                       do_skip(statok ? *_argv : "stdin", statok);
+               if (*_argv)
+                       ++_argv;
+               if (!bb_dump_skip)
+                       return 1;
+       }
+       /* NOTREACHED */
+}
+
+static unsigned char *get(void)
+{
+       static smallint ateof = 1;
+       static unsigned char *curp = NULL, *savp; /*DBU:[dave@cray.com]initialize curp */
+
+       int n;
+       int need, nread;
+       unsigned char *tmpp;
+
+       if (!curp) {
+               address = (off_t)0; /*DBU:[dave@cray.com] initialize,initialize..*/
+               curp = xmalloc(bb_dump_blocksize);
+               savp = xmalloc(bb_dump_blocksize);
+       } else {
+               tmpp = curp;
+               curp = savp;
+               savp = tmpp;
+               address = savaddress += bb_dump_blocksize;
+       }
+       for (need = bb_dump_blocksize, nread = 0;;) {
+               /*
+                * if read the right number of bytes, or at EOF for one file,
+                * and no other files are available, zero-pad the rest of the
+                * block and set the end flag.
+                */
+               if (!bb_dump_length || (ateof && !next((char **) NULL))) {
+                       if (need == bb_dump_blocksize) {
+                               return NULL;
+                       }
+                       if (bb_dump_vflag != ALL && !memcmp(curp, savp, nread)) {
+                               if (bb_dump_vflag != DUP) {
+                                       puts("*");
+                               }
+                               return NULL;
+                       }
+                       memset((char *) curp + nread, 0, need);
+                       eaddress = address + nread;
+                       return curp;
+               }
+               n = fread((char *) curp + nread, sizeof(unsigned char),
+                                 bb_dump_length == -1 ? need : MIN(bb_dump_length, need), stdin);
+               if (!n) {
+                       if (ferror(stdin)) {
+                               bb_simple_perror_msg(_argv[-1]);
+                       }
+                       ateof = 1;
+                       continue;
+               }
+               ateof = 0;
+               if (bb_dump_length != -1) {
+                       bb_dump_length -= n;
+               }
+               need -= n;
+               if (!need) {
+                       if (bb_dump_vflag == ALL || bb_dump_vflag == FIRST
+                               || memcmp(curp, savp, bb_dump_blocksize)) {
+                               if (bb_dump_vflag == DUP || bb_dump_vflag == FIRST) {
+                                       bb_dump_vflag = WAIT;
+                               }
+                               return curp;
+                       }
+                       if (bb_dump_vflag == WAIT) {
+                               puts("*");
+                       }
+                       bb_dump_vflag = DUP;
+                       address = savaddress += bb_dump_blocksize;
+                       need = bb_dump_blocksize;
+                       nread = 0;
+               } else {
+                       nread += n;
+               }
+       }
+}
+
+static void bpad(PR * pr)
+{
+       char *p1, *p2;
+
+       /*
+        * remove all conversion flags; '-' is the only one valid
+        * with %s, and it's not useful here.
+        */
+       pr->flags = F_BPAD;
+       *pr->cchar = 's';
+       for (p1 = pr->fmt; *p1 != '%'; ++p1);
+       for (p2 = ++p1; *p1 && strchr(" -0+#", *p1); ++p1)
+               if (pr->nospace) pr->nospace--;
+       while ((*p2++ = *p1++) != 0);
+}
+
+static const char conv_str[] 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';
+               (void) printf(pr->fmt, *p);
+       } else {
+               sprintf(buf, "%03o", (int) *p);
+               str = buf;
+         strpr:
+               *pr->cchar = 's';
+               printf(pr->fmt, str);
+       }
+}
+
+static void conv_u(PR * pr, unsigned char * p)
+{
+       static const char list[] 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(void)
+{
+/*  extern FU *endfu; */
+       FS *fs;
+       FU *fu;
+       PR *pr;
+       int cnt;
+       unsigned char *bp;
+
+       off_t saveaddress;
+       unsigned char savech = 0, *savebp;
+
+       while ((bp = get()) != NULL) {
+               for (fs = bb_dump_fshead, savebp = bp, saveaddress = address; fs;
+                        fs = fs->nextfs, bp = savebp, address = saveaddress) {
+                       for (fu = fs->nextfu; fu; fu = fu->nextfu) {
+                               if (fu->flags & F_IGNORE) {
+                                       break;
+                               }
+                               for (cnt = fu->reps; cnt; --cnt) {
+                                       for (pr = fu->nextpr; pr; address += pr->bcnt,
+                                                bp += pr->bcnt, pr = pr->nextpr) {
+                                               if (eaddress && address >= eaddress &&
+                                                       !(pr->flags & (F_TEXT | F_BPAD))) {
+                                                       bpad(pr);
+                                               }
+                                               if (cnt == 1 && pr->nospace) {
+                                                       savech = *pr->nospace;
+                                                       *pr->nospace = '\0';
+                                               }
+/*                      PRINT; */
+                                               switch (pr->flags) {
+                                               case F_ADDRESS:
+                                                       printf(pr->fmt, (unsigned int) address);
+                                                       break;
+                                               case F_BPAD:
+                                                       printf(pr->fmt, "");
+                                                       break;
+                                               case F_C:
+                                                       conv_c(pr, bp);
+                                                       break;
+                                               case F_CHAR:
+                                                       printf(pr->fmt, *bp);
+                                                       break;
+                                               case F_DBL:{
+                                                       double dval;
+                                                       float fval;
+
+                                                       switch (pr->bcnt) {
+                                                       case 4:
+                                                               memmove((char *) &fval, (char *) bp,
+                                                                         sizeof(fval));
+                                                               printf(pr->fmt, fval);
+                                                               break;
+                                                       case 8:
+                                                               memmove((char *) &dval, (char *) bp,
+                                                                         sizeof(dval));
+                                                               printf(pr->fmt, dval);
+                                                               break;
+                                                       }
+                                                       break;
+                                               }
+                                               case F_INT:{
+                                                       int ival;
+                                                       short sval;
+
+                                                       switch (pr->bcnt) {
+                                                       case 1:
+                                                               printf(pr->fmt, (int) *bp);
+                                                               break;
+                                                       case 2:
+                                                               memmove((char *) &sval, (char *) bp,
+                                                                         sizeof(sval));
+                                                               printf(pr->fmt, (int) sval);
+                                                               break;
+                                                       case 4:
+                                                               memmove((char *) &ival, (char *) bp,
+                                                                         sizeof(ival));
+                                                               printf(pr->fmt, ival);
+                                                               break;
+                                                       }
+                                                       break;
+                                               }
+                                               case F_P:
+                                                       printf(pr->fmt, isprint(*bp) ? *bp : '.');
+                                                       break;
+                                               case F_STR:
+                                                       printf(pr->fmt, (char *) bp);
+                                                       break;
+                                               case F_TEXT:
+                                                       printf(pr->fmt);
+                                                       break;
+                                               case F_U:
+                                                       conv_u(pr, bp);
+                                                       break;
+                                               case F_UINT:{
+                                                       unsigned int ival;
+                                                       unsigned short sval;
+
+                                                       switch (pr->bcnt) {
+                                                       case 1:
+                                                               printf(pr->fmt, (unsigned int) * bp);
+                                                               break;
+                                                       case 2:
+                                                               memmove((char *) &sval, (char *) bp,
+                                                                         sizeof(sval));
+                                                               printf(pr->fmt, (unsigned int) sval);
+                                                               break;
+                                                       case 4:
+                                                               memmove((char *) &ival, (char *) bp,
+                                                                         sizeof(ival));
+                                                               printf(pr->fmt, ival);
+                                                               break;
+                                                       }
+                                                       break;
+                                               }
+                                               }
+                                               if (cnt == 1 && pr->nospace) {
+                                                       *pr->nospace = savech;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+       if (endfu) {
+               /*
+                * if eaddress not set, error or file bb_dump_size was multiple of
+                * bb_dump_blocksize, and no partial block ever found.
+                */
+               if (!eaddress) {
+                       if (!address) {
+                               return;
+                       }
+                       eaddress = address;
+               }
+               for (pr = endfu->nextpr; pr; pr = pr->nextpr) {
+                       switch (pr->flags) {
+                       case F_ADDRESS:
+                               (void) printf(pr->fmt, (unsigned int) eaddress);
+                               break;
+                       case F_TEXT:
+                               (void) printf(pr->fmt);
+                               break;
+                       }
+               }
+       }
+}
+
+int bb_dump_dump(char **argv)
+{
+       FS *tfs;
+
+       /* figure out the data block bb_dump_size */
+       for (bb_dump_blocksize = 0, tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) {
+               tfs->bcnt = bb_dump_size(tfs);
+               if (bb_dump_blocksize < tfs->bcnt) {
+                       bb_dump_blocksize = tfs->bcnt;
+               }
+       }
+       /* rewrite the rules, do syntax checking */
+       for (tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) {
+               rewrite(tfs);
+       }
+
+       next(argv);
+       display();
+
+       return exitval;
+}
+
+void bb_dump_add(const char *fmt)
+{
+       const char *p;
+       char *p1;
+       char *p2;
+       static FS **nextfs;
+       FS *tfs;
+       FU *tfu, **nextfu;
+       const char *savep;
+
+       /* start new linked list of format units */
+       tfs = xzalloc(sizeof(FS)); /*DBU:[dave@cray.com] start out NULL */
+       if (!bb_dump_fshead) {
+               bb_dump_fshead = tfs;
+       } else {
+               *nextfs = tfs;
+       }
+       nextfs = &tfs->nextfs;
+       nextfu = &tfs->nextfu;
+
+       /* take the format string and break it up into format units */
+       for (p = fmt;;) {
+               /* bb_dump_skip leading white space */
+               p = skip_whitespace(p);
+               if (!*p) {
+                       break;
+               }
+
+               /* allocate a new format unit and link it in */
+               /* NOSTRICT */
+               /* DBU:[dave@cray.com] zalloc so that forward pointers start out NULL */
+               tfu = xzalloc(sizeof(FU));
+               *nextfu = tfu;
+               nextfu = &tfu->nextfu;
+               tfu->reps = 1;
+
+               /* if leading digit, repetition count */
+               if (isdigit(*p)) {
+                       for (savep = p; isdigit(*p); ++p);
+                       if (!isspace(*p) && *p != '/') {
+                               bb_error_msg_and_die("bad format {%s}", fmt);
+                       }
+                       /* may overwrite either white space or slash */
+                       tfu->reps = atoi(savep);
+                       tfu->flags = F_SETREP;
+                       /* bb_dump_skip trailing white space */
+                       p = skip_whitespace(++p);
+               }
+
+               /* bb_dump_skip slash and trailing white space */
+               if (*p == '/') {
+                       p = skip_whitespace(++p);
+               }
+
+               /* byte count */
+               if (isdigit(*p)) {
+// TODO: use bb_strtou
+                       savep = p;
+                       do p++; while (isdigit(*p));
+                       if (!isspace(*p)) {
+                               bb_error_msg_and_die("bad format {%s}", fmt);
+                       }
+                       tfu->bcnt = atoi(savep);
+                       /* bb_dump_skip trailing white space */
+                       p = skip_whitespace(++p);
+               }
+
+               /* format */
+               if (*p != '"') {
+                       bb_error_msg_and_die("bad format {%s}", fmt);
+               }
+               for (savep = ++p; *p != '"';) {
+                       if (*p++ == 0) {
+                               bb_error_msg_and_die("bad format {%s}", fmt);
+                       }
+               }
+               tfu->fmt = xmalloc(p - savep + 1);
+               strncpy(tfu->fmt, savep, p - savep);
+               tfu->fmt[p - savep] = '\0';
+/*      escape(tfu->fmt); */
+
+               p1 = tfu->fmt;
+
+               /* alphabetic escape sequences have to be done in place */
+               for (p2 = p1;; ++p1, ++p2) {
+                       if (!*p1) {
+                               *p2 = *p1;
+                               break;
+                       }
+                       if (*p1 == '\\') {
+                               const char *cs = conv_str + 4;
+                               ++p1;
+                               *p2 = *p1;
+                               do {
+                                       if (*p1 == cs[2]) {
+                                               *p2 = cs[0];
+                                               break;
+                                       }
+                                       cs += 4;
+                               } while (*cs);
+                       }
+               }
+
+               p++;
+       }
+}
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/libbb/error_msg.c b/libbb/error_msg.c
new file mode 100644 (file)
index 0000000..5f53f03
--- /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 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..0e99a03
--- /dev/null
@@ -0,0 +1,47 @@
+/* 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"
+
+int die_sleep;
+#if ENABLE_FEATURE_PREFER_APPLETS || ENABLE_HUSH
+jmp_buf die_jmp;
+#endif
+
+void 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);
+}
+
+void bb_error_msg_and_die(const char *s, ...)
+{
+       va_list p;
+
+       va_start(p, s);
+       bb_verror_msg(s, p, NULL);
+       va_end(p);
+       xfunc_die();
+}
diff --git a/libbb/execable.c b/libbb/execable.c
new file mode 100644 (file)
index 0000000..2649a6c
--- /dev/null
@@ -0,0 +1,71 @@
+/* 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 execable_file(const char *name)
+{
+       struct stat s;
+       return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode));
+}
+
+/* search $PATH for an executable file;
+ * return allocated string containing full path if found;
+ * return NULL otherwise;
+ */
+char *find_execable(const char *filename)
+{
+       char *path, *p, *n;
+
+       p = path = xstrdup(getenv("PATH"));
+       while (p) {
+               n = strchr(p, ':');
+               if (n)
+                       *n++ = '\0';
+               if (*p != '\0') { /* it's not a PATH="foo::bar" situation */
+                       p = concat_path_file(p, filename);
+                       if (execable_file(p)) {
+                               free(path);
+                               return p;
+                       }
+                       free(p);
+               }
+               p = n;
+       }
+       free(path);
+       return NULL;
+}
+
+/* search $PATH for an executable file;
+ * return 1 if found;
+ * return 0 otherwise;
+ */
+int exists_execable(const char *filename)
+{
+       char *ret = find_execable(filename);
+       if (ret) {
+               free(ret);
+               return 1;
+       }
+       return 0;
+}
+
+#if ENABLE_FEATURE_PREFER_APPLETS
+/* just like the real execvp, but try to launch an applet named 'file' first
+ */
+int 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..768ee94
--- /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 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..9f05500
--- /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 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..d6fada1
--- /dev/null
@@ -0,0 +1,66 @@
+/* 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)
+{
+       char *linebuf = NULL;
+       const int term_length = strlen(terminating_string);
+       int end_string_offset;
+       int linebufsz = 0;
+       int idx = 0;
+       int ch;
+
+       while (1) {
+               ch = fgetc(file);
+               if (ch == EOF) {
+                       if (idx == 0)
+                               return linebuf; /* NULL */
+                       break;
+               }
+
+               if (idx >= linebufsz) {
+                       linebufsz += 200;
+                       linebuf = xrealloc(linebuf, linebufsz);
+               }
+
+               linebuf[idx] = ch;
+               idx++;
+
+               /* Check for terminating string */
+               end_string_offset = idx - term_length;
+               if (end_string_offset >= 0
+                && memcmp(&linebuf[end_string_offset], terminating_string, term_length) == 0
+               ) {
+                       if (chop_off)
+                               idx -= term_length;
+                       break;
+               }
+       }
+       /* Grow/shrink *first*, then store NUL */
+       linebuf = xrealloc(linebuf, idx + 1);
+       linebuf[idx] = '\0';
+       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 *xmalloc_fgets_str(FILE *file, const char *terminating_string)
+{
+       return xmalloc_fgets_internal(file, terminating_string, 0);
+}
+
+char *xmalloc_fgetline_str(FILE *file, const char *terminating_string)
+{
+       return xmalloc_fgets_internal(file, terminating_string, 1);
+}
diff --git a/libbb/find_mount_point.c b/libbb/find_mount_point.c
new file mode 100644 (file)
index 0000000..cb00b98
--- /dev/null
@@ -0,0 +1,53 @@
+/* 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 *find_mount_point(const char *name, const char *table)
+{
+       struct stat s;
+       dev_t mountDevice;
+       FILE *mountTable;
+       struct mntent *mountEntry;
+
+       if (stat(name, &s) != 0)
+               return 0;
+
+       if ((s.st_mode & S_IFMT) == S_IFBLK)
+               mountDevice = s.st_rdev;
+       else
+               mountDevice = s.st_dev;
+
+
+       mountTable = setmntent(table ? table : bb_path_mtab_file, "r");
+       if (!mountTable)
+               return 0;
+
+       while ((mountEntry = getmntent(mountTable)) != 0) {
+               if (strcmp(name, mountEntry->mnt_dir) == 0
+                || strcmp(name, mountEntry->mnt_fsname) == 0
+               ) { /* String match. */
+                       break;
+               }
+               if (stat(mountEntry->mnt_fsname, &s) == 0 && s.st_rdev == mountDevice)  /* Match the device. */
+                       break;
+               if (stat(mountEntry->mnt_dir, &s) == 0 && s.st_dev == mountDevice)      /* Match the directory's mount point. */
+                       break;
+       }
+       endmntent(mountTable);
+       return mountEntry;
+}
diff --git a/libbb/find_pid_by_name.c b/libbb/find_pid_by_name.c
new file mode 100644 (file)
index 0000000..8dcdb13
--- /dev/null
@@ -0,0 +1,92 @@
+/* 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".
+*/
+
+/* find_pid_by_name()
+ *
+ *  Modified by Vladimir Oleynik for use with libbb/procps.c
+ *  This finds the pid of the specified process.
+ *  Currently, it's implemented by rummaging through
+ *  the proc filesystem.
+ *
+ *  Returns a list of all matching PIDs
+ *  It is the caller's duty to free the returned pidlist.
+ */
+pid_t* find_pid_by_name(const char* procName)
+{
+       pid_t* pidList;
+       int i = 0;
+       procps_status_t* p = NULL;
+
+       pidList = xmalloc(sizeof(*pidList));
+       while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_COMM|PSSCAN_ARGV0))) {
+               if (
+               /* 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 */
+                   (!p->comm[sizeof(p->comm)-2] && strcmp(p->comm, procName) == 0)
+               /* or we require argv0 to match (essential for matching reexeced /proc/self/exe)*/
+                || (p->argv0 && strcmp(bb_basename(p->argv0), procName) == 0)
+               /* TOOD: we can also try /proc/NUM/exe link, do we want that? */
+               ) {
+                       pidList = xrealloc(pidList, sizeof(*pidList) * (i+2));
+                       pidList[i++] = p->pid;
+               }
+       }
+
+       pidList[i] = 0;
+       return pidList;
+}
+
+pid_t *pidlist_reverse(pid_t *pidList)
+{
+       int i = 0;
+       while (pidList[i])
+               i++;
+       if (--i >= 0) {
+               pid_t k;
+               int j;
+               for (j = 0; i > j; i--, j++) {
+                       k = pidList[i];
+                       pidList[i] = pidList[j];
+                       pidList[j] = k;
+               }
+       }
+       return pidList;
+}
diff --git a/libbb/find_root_device.c b/libbb/find_root_device.c
new file mode 100644 (file)
index 0000000..9779f7e
--- /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 *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..7503c8b
--- /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 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..0da27b1
--- /dev/null
@@ -0,0 +1,72 @@
+/* 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 <sys/ioctl.h>
+#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 get_console_fd(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("can't open console");
+       return fd;                      /* total failure */
+}
diff --git a/libbb/get_last_path_component.c b/libbb/get_last_path_component.c
new file mode 100644 (file)
index 0000000..0f60215
--- /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 *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 *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..ac4d14b
--- /dev/null
@@ -0,0 +1,69 @@
+/* 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.
+ */
+
+#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 read is stored in it.
+ * Return NULL if EOF/error */
+char *bb_get_chunk_from_file(FILE *file, int *end)
+{
+       int ch;
+       int idx = 0;
+       char *linebuf = NULL;
+       int linebufsz = 0;
+
+       while ((ch = getc(file)) != EOF) {
+               /* grow the line buffer as necessary */
+               if (idx >= linebufsz) {
+                       linebufsz += 80;
+                       linebuf = xrealloc(linebuf, linebufsz);
+               }
+               linebuf[idx++] = (char) ch;
+               if (!ch || (end && ch == '\n'))
+                       break;
+       }
+       if (end)
+               *end = idx;
+       if (linebuf) {
+               // huh, does fgets discard prior data on error like this?
+               // I don't think so....
+               //if (ferror(file)) {
+               //      free(linebuf);
+               //      return NULL;
+               //}
+               linebuf = xrealloc(linebuf, idx+1);
+               linebuf[idx] = '\0';
+       }
+       return linebuf;
+}
+
+/* Get line, including trailing \n if any */
+char *xmalloc_fgets(FILE *file)
+{
+       int i;
+
+       return bb_get_chunk_from_file(file, &i);
+}
+
+/* Get line.  Remove trailing \n */
+char *xmalloc_getline(FILE *file)
+{
+       int i;
+       char *c = bb_get_chunk_from_file(file, &i);
+
+       if (i && c[--i] == '\n')
+               c[i] = '\0';
+
+       return c;
+}
diff --git a/libbb/getopt32.c b/libbb/getopt32.c
new file mode 100644 (file)
index 0000000..51e0306
--- /dev/null
@@ -0,0 +1,594 @@
+/* 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(my_b->data); my_b = my_b->link; }
+        if (my_b)        // but llist is stored if -b is specified
+                free_llist(my_b);
+        if (verbose_level) printf("verbose level is %d\n", verbose_level);
+
+Special characters:
+
+ "-"    A dash as the first char in a opt_complementary group forces
+        all arguments to be treated as options, even if they have
+        no leading dashes. Next char in this case can't be a digit (0-9),
+        use ':' or end of line. For example:
+
+        opt_complementary = "-:w-x:x-w";
+        getopt32(argv, "wx");
+
+        Allows any arguments to be given without a dash (./program w x)
+        as well as with a dash (./program -x).
+
+ "--"   A double dash at the beginning of opt_complementary means the
+        argv[1] string should always be treated as options, even if it isn't
+        prefixed with a "-".  This is useful for special syntax in applets
+        such as "ar" and "tar":
+        tar xvf foo.tar
+
+ "-N"   A dash as the first char in a opt_complementary group followed
+        by a single digit (0-9) means that at least N non-option
+        arguments must be present on the command line
+
+ "=N"   An equal sign as the first char in a opt_complementary group followed
+        by a single digit (0-9) means that exactly N non-option
+        arguments must be present on the command line
+
+ "?N"   A "?" as the first char in a opt_complementary group followed
+        by a single digit (0-9) means that at most N arguments must be present
+        on the command line.
+
+ "V-"   An option with dash before colon or end-of-line results in
+        bb_show_usage being called if this option is encountered.
+        This is typically used to implement "print verbose usage message
+        and exit" option.
+
+ "a-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
+getopt32(char **argv, const char *applet_opts, ...)
+{
+       int argc;
+       unsigned flags = 0;
+       unsigned requires = 0;
+       t_complementary complementary[33];
+       int c;
+       const unsigned char *s;
+       t_complementary *on_off;
+       va_list p;
+#if ENABLE_GETOPT_LONG
+       const struct option *l_o;
+       struct option *long_options = (struct option *) &bb_null_long_options;
+#endif
+       unsigned trigger;
+       char **pargv = NULL;
+       int min_arg = 0;
+       int max_arg = -1;
+
+#define SHOW_USAGE_IF_ERROR     1
+#define ALL_ARGV_IS_OPTS        2
+#define FIRST_ARGV_IS_OPT       4
+#define FREE_FIRST_ARGV_IS_OPT  8
+       int spec_flgs = 0;
+
+       argc = 0;
+       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) {
+               if (argv[1] && argv[1][0] != '-' && argv[1][0] != '\0') {
+                       argv[1] = xasprintf("-%s", argv[1]);
+                       if (ENABLE_FEATURE_CLEAN_UP)
+                               spec_flgs |= FREE_FIRST_ARGV_IS_OPT;
+               }
+       }
+
+       /* In case 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 = 0; optopt = 0; - do we need this?? */
+
+       /* 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
+               c &= 0xff; /* fight libc's sign extension */
+ loop_arg_is_opt:
+               for (on_off = complementary; on_off->opt_char != c; on_off++) {
+                       /* c==0 if long opt have non NULL flag */
+                       if (on_off->opt_char == '\0' && c != '\0')
+                               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)
+                               *(unsigned*)(on_off->optarg) = xatoi_u(optarg);
+               } else if (on_off->optarg) {
+                       if (optarg)
+                               *(char **)(on_off->optarg) = optarg;
+               }
+               if (pargv != NULL)
+                       break;
+       }
+
+       if (spec_flgs & ALL_ARGV_IS_OPTS) {
+               /* process argv is option, for example "ps" applet */
+               if (pargv == NULL)
+                       pargv = argv + optind;
+               while (*pargv) {
+                       c = **pargv;
+                       if (c == '\0') {
+                               pargv++;
+                       } else {
+                               (*pargv)++;
+                               goto loop_arg_is_opt;
+                       }
+               }
+       }
+
+#if (ENABLE_AR || ENABLE_TAR) && ENABLE_FEATURE_CLEAN_UP
+       if (spec_flgs & FREE_FIRST_ARGV_IS_OPT)
+               free(argv[1]);
+#endif
+       /* check depending requires for given options */
+       for (on_off = complementary; on_off->opt_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..5ac9582
--- /dev/null
@@ -0,0 +1,58 @@
+/* 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 getpty(char *line)
+{
+       int p;
+#if ENABLE_FEATURE_DEVPTS
+       p = open("/dev/ptmx", O_RDWR);
+       if (p > 0) {
+               const char *name;
+               grantpt(p);
+               unlockpt(p);
+               name = ptsname(p);
+               if (!name) {
+                       bb_perror_msg("ptsname error (is /dev/pts mounted?)");
+                       return -1;
+               }
+               safe_strncpy(line, name, GETPTY_BUFSIZE);
+               return p;
+       }
+#else
+       struct stat stb;
+       int i;
+       int j;
+
+       strcpy(line, "/dev/ptyXX");
+
+       for (i = 0; i < 16; i++) {
+               line[8] = "pqrstuvwxyzabcde"[i];
+               line[9] = '0';
+               if (stat(line, &stb) < 0) {
+                       continue;
+               }
+               for (j = 0; j < 16; j++) {
+                       line[9] = j < 10 ? j + '0' : j - 10 + 'a';
+                       if (DEBUG)
+                               fprintf(stderr, "Trying to open device: %s\n", line);
+                       p = open(line, O_RDWR | O_NOCTTY);
+                       if (p >= 0) {
+                               line[5] = 't';
+                               return p;
+                       }
+               }
+       }
+#endif /* FEATURE_DEVPTS */
+       return -1;
+}
+
+
diff --git a/libbb/herror_msg.c b/libbb/herror_msg.c
new file mode 100644 (file)
index 0000000..264690b
--- /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 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..894c80f
--- /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 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..d60ef61
--- /dev/null
@@ -0,0 +1,88 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * June 30, 2001                 Manuel Novoa III
+ *
+ * All-integer version (hey, not everyone has floating point) of
+ * make_human_readable_str, modified from similar code I had written
+ * for busybox several months ago.
+ *
+ * Notes:
+ *   1) I'm using an unsigned long long to hold the product size * block_size,
+ *      as df (which calls this routine) could request a representation of a
+ *      partition size in bytes > max of unsigned long.  If long longs aren't
+ *      available, it would be possible to do what's needed using polynomial
+ *      representations (say, powers of 1024) and manipulating coefficients.
+ *      The base ten "bytes" output could be handled similarly.
+ *
+ *   2) This routine always outputs a decimal point and a tenths digit when
+ *      display_unit != 0.  Hence, it isn't uncommon for the returned string
+ *      to have a length of 5 or 6.
+ *
+ *      It might be nice to add a flag to indicate no decimal digits in
+ *      that case.  This could be either an additional parameter, or a
+ *      special value of display_unit.  Such a flag would also be nice for du.
+ *
+ *      Some code to omit the decimal point and tenths digit is sketched out
+ *      and "#if 0"'d below.
+ */
+
+#include "libbb.h"
+
+const char *make_human_readable_str(unsigned long long size,
+       unsigned long block_size, unsigned long display_unit)
+{
+       /* The code will adjust for additional (appended) units */
+       static const char zero_and_units[] ALIGN1 = { '0', 0, 'k', 'M', 'G', 'T' };
+       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;
+
+       u = zero_and_units;
+       f = fmt;
+       frac = 0;
+
+       val = size * block_size;
+       if (val == 0) {
+               return u;
+       }
+
+       if (display_unit) {
+               val += display_unit/2;  /* Deal with rounding */
+               val /= display_unit;    /* Don't combine with the line above!!! */
+       } else {
+               ++u;
+               while ((val >= 1024)
+                && (u < zero_and_units + sizeof(zero_and_units) - 1)
+               ) {
+                       f = fmt_tenths;
+                       ++u;
+                       frac = (((int)(val % 1024)) * 10 + 1024/2) / 1024;
+                       val /= 1024;
+               }
+               if (frac >= 10) {               /* We need to round up here. */
+                       ++val;
+                       frac = 0;
+               }
+#if 0
+               /* Sample code to omit decimal point and tenths digit. */
+               if (/* no_tenths */ 1) {
+                       if (frac >= 5) {
+                               ++val;
+                       }
+                       f = "%llu%*c" /* fmt_no_tenths */;
+                       frac = 1;
+               }
+#endif
+       }
+
+       /* If f==fmt then 'frac' and 'u' are ignored. */
+       snprintf(str, sizeof(str), f, val, frac, *u);
+
+       return str;
+}
diff --git a/libbb/inet_common.c b/libbb/inet_common.c
new file mode 100644 (file)
index 0000000..9c4f496
--- /dev/null
@@ -0,0 +1,224 @@
+/* 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 "libbb.h"
+#include "inet_common.h"
+
+int 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;
+#endif
+
+#ifdef 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 *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 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 *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..3231bc8
--- /dev/null
@@ -0,0 +1,30 @@
+/* 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 bb_info_msg(const char *s, ...)
+{
+       va_list p;
+       /* va_copy is used because it is not portable
+        * to use va_list p twice */
+       va_list p2;
+
+       va_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);
+}
diff --git a/libbb/inode_hash.c b/libbb/inode_hash.c
new file mode 100644 (file)
index 0000000..9cca74b
--- /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 *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 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_FEATURE_CLEAN_UP
+/* Clear statbuf hash table */
+void 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..1d2477f
--- /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 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..50b82ae
--- /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 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..aaa85dd
--- /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* 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..b05319a
--- /dev/null
@@ -0,0 +1,1881 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Termios command line History and Editing.
+ *
+ * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license.
+ * Written by:   Vladimir Oleynik <dzo@simtreas.ru>
+ *
+ * Used ideas:
+ *      Adam Rogoyski    <rogoyski@cs.utexas.edu>
+ *      Dave Cinege      <dcinege@psychosis.com>
+ *      Jakub Jelinek (c) 1995
+ *      Erik Andersen    <andersen@codepoet.org> (Majorly adjusted for busybox)
+ *
+ * This code is 'as is' with no warranty.
+ */
+
+/*
+   Usage and known bugs:
+   Terminal key codes are not extensive, and more will probably
+   need to be added. This version was created on Debian GNU/Linux 2.x.
+   Delete, Backspace, Home, End, and the arrow keys were tested
+   to work in an Xterm and console. Ctrl-A also works as Home.
+   Ctrl-E also works as End.
+
+   Small bugs (simple effect):
+   - not true viewing if terminal size (x*y symbols) less
+     size (prompt + editor's line + 2 symbols)
+   - not true viewing if length prompt less terminal width
+ */
+
+#include "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 statics {
+       line_input_t *state;
+
+       volatile unsigned cmdedit_termw; /* = 80; */ /* actual terminal width */
+       sighandler_t previous_SIGWINCH_handler;
+
+
+       int cmdedit_x;           /* real x terminal position */
+       int cmdedit_y;           /* pseudoreal y terminal position */
+       int 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
+};
+
+/* Make it reside in writable memory, yet make compiler understand
+ * that it is not going to change. */
+static struct statics *const ptr_to_statics __attribute__ ((section (".data")));
+
+#define S (*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 statics**)&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(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' */
+static void cmdedit_set_out_char(int next_char)
+{
+       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;
+               /* destroy "(auto)margin" */
+               bb_putchar(next_char);
+               bb_putchar('\b');
+       }
+// 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 mysteriously
+                        * miscompiled for some ARM users.
+                        * 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;
+       count_y = 1 + (num / cmdedit_termw);
+       cmdedit_y -= count_y;
+       cmdedit_x = cmdedit_termw * 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);
+       cmdedit_x = cmdedit_prmt_len;
+       cursor = 0;
+// Huh? what if cmdedit_prmt_len >= width?
+       cmdedit_y = 0;                  /* new quasireal y */
+}
+
+/* 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 == 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
+
+       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)
+{
+       int nm = num_matches;
+       int nm1 = nm + 1;
+
+       matches = xrealloc(matches, nm1 * sizeof(char *));
+       matches[nm] = 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;
+               int 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) {
+                       int i, 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 */
+
+
+#if MAX_HISTORY > 0
+
+/* state->flags is already checked to be nonzero */
+static void get_previous_history(void)
+{
+       if (command_ps[0] != '\0' || state->history[state->cur_history] == NULL) {
+               free(state->history[state->cur_history]);
+               state->history[state->cur_history] = xstrdup(command_ps);
+       }
+       state->cur_history--;
+}
+
+static int get_next_history(void)
+{
+       if (state->flags & DO_HISTORY) {
+               int ch = state->cur_history;
+               if (ch < state->cnt_history) {
+                       get_previous_history(); /* save the current history line */
+                       state->cur_history = ch + 1;
+                       return state->cur_history;
+               }
+       }
+       beep();
+       return 0;
+}
+
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+/* state->flags is already checked to be nonzero */
+static void load_history(const char *fromfile)
+{
+       FILE *fp;
+       int hi;
+
+       /* cleanup old */
+       for (hi = state->cnt_history; hi > 0;) {
+               hi--;
+               free(state->history[hi]);
+       }
+
+       fp = fopen(fromfile, "r");
+       if (fp) {
+               for (hi = 0; hi < MAX_HISTORY;) {
+                       char *hl = xmalloc_getline(fp);
+                       int l;
+
+                       if (!hl)
+                               break;
+                       l = strlen(hl);
+                       if (l >= MAX_LINELEN)
+                               hl[MAX_LINELEN-1] = '\0';
+                       if (l == 0 || hl[0] == ' ') {
+                               free(hl);
+                               continue;
+                       }
+                       state->history[hi++] = hl;
+               }
+               fclose(fp);
+       }
+       state->cur_history = state->cnt_history = hi;
+}
+
+/* state->flags is already checked to be nonzero */
+static void save_history(const char *tofile)
+{
+       FILE *fp;
+
+       fp = fopen(tofile, "w");
+       if (fp) {
+               int i;
+
+               for (i = 0; i < state->cnt_history; i++) {
+                       fprintf(fp, "%s\n", state->history[i]);
+               }
+               fclose(fp);
+       }
+}
+#else
+#define load_history(a) ((void)0)
+#define save_history(a) ((void)0)
+#endif /* FEATURE_COMMAND_SAVEHISTORY */
+
+static void remember_in_history(const char *str)
+{
+       int i;
+
+       if (!(state->flags & DO_HISTORY))
+               return;
+
+       i = state->cnt_history;
+       free(state->history[MAX_HISTORY]);
+       state->history[MAX_HISTORY] = NULL;
+       /* After max history, remove the oldest command */
+       if (i >= MAX_HISTORY) {
+               free(state->history[0]);
+               for (i = 0; i < MAX_HISTORY-1; i++)
+                       state->history[i] = state->history[i+1];
+       }
+// Maybe "if (!i || strcmp(history[i-1], command) != 0) ..."
+// (i.e. do not save dups?)
+       state->history[i++] = xstrdup(str);
+       state->cur_history = i;
+       state->cnt_history = i;
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+       if ((state->flags & SAVE_HISTORY) && state->hist_file)
+               save_history(state->hist_file);
+#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)
+{
+       int 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 read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st)
+{
+#if ENABLE_FEATURE_TAB_COMPLETION
+       smallint lastWasTab = FALSE;
+#endif
+       unsigned int 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 */
+               int len;
+               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)
+               load_history(state->hist_file);
+#endif
+
+       /* 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_FILENO, 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
+       /* 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 ((state->flags & DO_HISTORY) && state->cur_history > 0) {
+                               get_previous_history();
+                               goto rewrite_line;
+                       }
+                       beep();
+                       break;
+#endif
+
+               case CTRL('U'):
+               vi_case(CTRL('U')|vbit:)
+                       /* Control-U -- Clear line before cursor */
+                       if (cursor) {
+                               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 ((state->flags & DO_HISTORY) && state->cur_history > 0) {
+                                       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 (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_FILENO, TCSANOW, &initial_settings);
+       /* restore SIGWINCH handler */
+       signal(SIGWINCH, previous_SIGWINCH_handler);
+       fflush(stdout);
+
+       DEINIT_S();
+
+       return command_len;
+}
+
+line_input_t *new_line_input_t(int flags)
+{
+       line_input_t *n = xzalloc(sizeof(*n));
+       n->flags = flags;
+       return n;
+}
+
+#else
+
+#undef read_line_input
+int 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/llist.c b/libbb/llist.c
new file mode 100644 (file)
index 0000000..4b3971b
--- /dev/null
@@ -0,0 +1,108 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * linked list helper functions.
+ *
+ * Copyright (C) 2003 Glenn McGrath
+ * Copyright (C) 2005 Vladimir Oleynik
+ * Copyright (C) 2005 Bernhard Fischer
+ * Copyright (C) 2006 Rob Landley <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 llist_add_to(llist_t **old_head, void *data)
+{
+       llist_t *new_head = xmalloc(sizeof(llist_t));
+
+       new_head->data = data;
+       new_head->link = *old_head;
+       *old_head = new_head;
+}
+
+/* Add data to the end of the linked list.  */
+void llist_add_to_end(llist_t **list_head, void *data)
+{
+       llist_t *new_item = xmalloc(sizeof(llist_t));
+
+       new_item->data = data;
+       new_item->link = NULL;
+
+       if (!*list_head)
+               *list_head = new_item;
+       else {
+               llist_t *tail = *list_head;
+
+               while (tail->link)
+                       tail = tail->link;
+               tail->link = new_item;
+       }
+}
+
+/* Remove first element from the list and return it */
+void *llist_pop(llist_t **head)
+{
+       void *data, *next;
+
+       if (!*head)
+               return NULL;
+
+       data = (*head)->data;
+       next = (*head)->link;
+       free(*head);
+       *head = next;
+
+       return data;
+}
+
+/* Unlink arbitrary given element from the list */
+void llist_unlink(llist_t **head, llist_t *elm)
+{
+       llist_t *crt;
+
+       if (!(elm && *head))
+               return;
+
+       if (elm == *head) {
+               *head = (*head)->link;
+               return;
+       }
+
+       for (crt = *head; crt; crt = crt->link) {
+               if (crt->link == elm) {
+                       crt->link = elm->link;
+                       return;
+               }
+       }
+}
+
+/* Recursively free all elements in the linked list.  If freeit != NULL
+ * call it on each datum in the list */
+void llist_free(llist_t *elm, void (*freeit) (void *data))
+{
+       while (elm) {
+               void *data = llist_pop(&elm);
+
+               if (freeit)
+                       freeit(data);
+       }
+}
+
+#ifdef UNUSED
+/* Reverse list order. */
+llist_t *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;
+}
+#endif
diff --git a/libbb/login.c b/libbb/login.c
new file mode 100644 (file)
index 0000000..a711a54
--- /dev/null
@@ -0,0 +1,130 @@
+/* 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 print_login_issue(const char *issue_file, const char *tty)
+{
+       FILE *fd;
+       int c;
+       char buf[256+1];
+       const char *outbuf;
+       time_t t;
+       struct utsname uts;
+
+       time(&t);
+       uname(&uts);
+
+       puts("\r");     /* start a new line */
+
+       fd = fopen(issue_file, "r");
+       if (!fd)
+               return;
+       while ((c = fgetc(fd)) != EOF) {
+               outbuf = buf;
+               buf[0] = c;
+               buf[1] = '\0';
+               if (c == '\n') {
+                       buf[1] = '\r';
+                       buf[2] = '\0';
+               }
+               if (c == '\\' || c == '%') {
+                       c = fgetc(fd);
+                       switch (c) {
+                       case 's':
+                               outbuf = uts.sysname;
+                               break;
+                       case 'n':
+                       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':
+                               c = getdomainname(buf, sizeof(buf) - 1);
+                               buf[c >= 0 ? c : 0] = '\0';
+                               break;
+                       case 'd':
+                               strftime(buf, sizeof(buf), fmtstr_d, localtime(&t));
+                               break;
+                       case 't':
+                               strftime(buf, sizeof(buf), fmtstr_t, localtime(&t));
+                               break;
+                       case 'l':
+                               outbuf = tty;
+                               break;
+                       default:
+                               buf[0] = c;
+                       }
+               }
+               fputs(outbuf, stdout);
+       }
+       fclose(fd);
+       fflush(stdout);
+}
+
+void 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 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..6934b7a
--- /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 *query_loop(const char *device)
+{
+       int fd;
+       bb_loop_info loopinfo;
+       char *dev = 0;
+
+       fd = open(device, O_RDONLY);
+       if (fd < 0) return 0;
+       if (!ioctl(fd, BB_LOOP_GET_STATUS, &loopinfo))
+               dev = xasprintf("%ld %s", (long) loopinfo.lo_offset,
+                               (char *)loopinfo.lo_file_name);
+       close(fd);
+
+       return dev;
+}
+
+
+int del_loop(const char *device)
+{
+       int fd, rc;
+
+       fd = open(device, O_RDONLY);
+       if (fd < 0) return 1;
+       rc = ioctl(fd, LOOP_CLR_FD, 0);
+       close(fd);
+
+       return rc;
+}
+
+/* Returns 0 if mounted RW, 1 if mounted read-only, <0 for error.
+   *device is loop device to use, or if *device==NULL finds a loop device to
+   mount it on and sets *device to a strdup of that loop device name.  This
+   search will re-use an existing loop device already bound to that
+   file/offset if it finds one.
+ */
+int set_loop(char **device, const char *file, unsigned long long offset)
+{
+       char dev[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..8841c95
--- /dev/null
@@ -0,0 +1,103 @@
+/* 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 bb_make_directory (char *path, long mode, int flags)
+{
+       mode_t mask;
+       const char *fail_msg;
+       char *s = path;
+       char c;
+       struct stat st;
+
+       mask = umask(0);
+       if (mode == -1) {
+               umask(mask);
+               mode = (S_IXUSR | S_IXGRP | S_IXOTH |
+                               S_IWUSR | S_IWGRP | S_IWOTH |
+                               S_IRUSR | S_IRGRP | S_IROTH) & ~mask;
+       } else {
+               umask(mask & ~0300);
+       }
+
+       do {
+               c = 0;
+
+               if (flags & FILEUTILS_RECUR) {  /* Get the parent. */
+                       /* Bypass leading non-'/'s and then subsequent '/'s. */
+                       while (*s) {
+                               if (*s == '/') {
+                                       do {
+                                               ++s;
+                                       } while (*s == '/');
+                                       c = *s;         /* Save the current char */
+                                       *s = 0;         /* and replace it with nul. */
+                                       break;
+                               }
+                               ++s;
+                       }
+               }
+
+               if (mkdir(path, 0777) < 0) {
+                       /* If we failed for any other reason than the directory
+                        * already exists, output a diagnostic and return -1.*/
+                       if (errno != EEXIST
+                               || !(flags & FILEUTILS_RECUR)
+                               || (stat(path, &st) < 0 || !S_ISDIR(st.st_mode))) {
+                               fail_msg = "create";
+                               umask(mask);
+                               break;
+                       }
+                       /* Since the directory exists, don't attempt to change
+                        * permissions if it was the full target.  Note that
+                        * this is not an error conditon. */
+                       if (!c) {
+                               umask(mask);
+                               return 0;
+                       }
+               }
+
+               if (!c) {
+                       /* Done.  If necessary, updated perms on the newly
+                        * created directory.  Failure to update here _is_
+                        * an error.*/
+                       umask(mask);
+                       if ((mode != -1) && (chmod(path, mode) < 0)){
+                               fail_msg = "set permissions of";
+                               break;
+                       }
+                       return 0;
+               }
+
+               /* Remove any inserted nul from the path (recursive mode). */
+               *s = c;
+
+       } while (1);
+
+       bb_perror_msg("cannot %s directory '%s'", fail_msg, path);
+       return -1;
+}
diff --git a/libbb/makedev.c b/libbb/makedev.c
new file mode 100644 (file)
index 0000000..efd5122
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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 <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 bb_makedev(unsigned int major, unsigned int minor);
+unsigned long long 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..bd4dbb0
--- /dev/null
@@ -0,0 +1,44 @@
+/* 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 0 for a match, otherwise -1
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+int match_fstype(const struct mntent *mt, const char *fstype)
+{
+       int no = 0;
+       int len;
+
+       if (!mt)
+               return -1;
+
+       if (!fstype)
+               return 0;
+
+       if (fstype[0] == 'n' && fstype[1] == 'o') {
+               no = -1;
+               fstype += 2;
+       }
+
+       len = strlen(mt->mnt_type);
+       while (fstype) {
+               if (!strncmp(mt->mnt_type, fstype, len)
+                && (!fstype[len] || fstype[len] == ',')
+               ) {
+                       return no;
+               }
+               fstype = strchr(fstype, ',');
+               if (fstype)
+                       fstype++;
+       }
+
+       return -(no + 1);
+}
diff --git a/libbb/md5.c b/libbb/md5.c
new file mode 100644 (file)
index 0000000..9de37b9
--- /dev/null
@@ -0,0 +1,450 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  md5.c - Compute MD5 checksum of strings according to the
+ *          definition of MD5 in RFC 1321 from April 1992.
+ *
+ *  Written by Ulrich Drepper <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"
+
+#if CONFIG_MD5_SIZE_VS_SPEED < 0 || CONFIG_MD5_SIZE_VS_SPEED > 3
+# define MD5_SIZE_VS_SPEED 2
+#else
+# define MD5_SIZE_VS_SPEED CONFIG_MD5_SIZE_VS_SPEED
+#endif
+
+/* Initialize structure containing state of computation.
+ * (RFC 1321, 3.3: Step 3)
+ */
+void md5_begin(md5_ctx_t *ctx)
+{
+       ctx->A = 0x67452301;
+       ctx->B = 0xefcdab89;
+       ctx->C = 0x98badcfe;
+       ctx->D = 0x10325476;
+
+       ctx->total = 0;
+       ctx->buflen = 0;
+}
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+ * and defined in the RFC 1321.  The first function is a little bit optimized
+ * (as found in Colin Plumbs public domain implementation).
+ * #define FF(b, c, d) ((b & c) | (~b & d))
+ */
+# define FF(b, c, d) (d ^ (b & (c ^ d)))
+# define FG(b, c, d) FF (d, b, c)
+# define FH(b, c, d) (b ^ c ^ d)
+# define FI(b, c, d) (c ^ (b | ~d))
+
+/* Hash a single block, 64 bytes long and 4-byte aligned. */
+static void md5_hash_block(const void *buffer, md5_ctx_t *ctx)
+{
+       uint32_t correct_words[16];
+       const uint32_t *words = buffer;
+
+# if MD5_SIZE_VS_SPEED > 0
+       static const uint32_t C_array[] = {
+               /* round 1 */
+               0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+               0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+               0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+               0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+               /* round 2 */
+               0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+               0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8,
+               0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+               0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+               /* round 3 */
+               0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+               0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+               0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
+               0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+               /* round 4 */
+               0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+               0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+               0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+               0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+       };
+
+       static const char P_array[] 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       /* MD5_SIZE_VS_SPEED > 1 */
+               1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,   /* 2 */
+               5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,   /* 3 */
+               0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9    /* 4 */
+       };
+
+#  if MD5_SIZE_VS_SPEED > 1
+       static const char S_array[] 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
+#  define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
+
+               const uint32_t *pc;
+               const char *pp;
+               const char *ps;
+               int i;
+               uint32_t temp;
+
+               for (i = 0; i < 16; i++) {
+                       cwp[i] = SWAP_LE32(words[i]);
+               }
+               words += 16;
+
+#  if MD5_SIZE_VS_SPEED > 2
+               pc = C_array;
+               pp = P_array;
+               ps = S_array - 4;
+
+               for (i = 0; i < 64; i++) {
+                       if ((i & 0x0f) == 0)
+                               ps += 4;
+                       temp = A;
+                       switch (i >> 4) {
+                       case 0:
+                               temp += FF(B, C, D);
+                               break;
+                       case 1:
+                               temp += FG(B, C, D);
+                               break;
+                       case 2:
+                               temp += FH(B, C, D);
+                               break;
+                       case 3:
+                               temp += FI(B, C, D);
+                       }
+                       temp += cwp[(int) (*pp++)] + *pc++;
+                       CYCLIC(temp, ps[i & 3]);
+                       temp += B;
+                       A = D;
+                       D = C;
+                       C = B;
+                       B = temp;
+               }
+#  else
+               pc = C_array;
+               pp = P_array;
+               ps = S_array;
+
+               for (i = 0; i < 16; i++) {
+                       temp = A + FF(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+                       CYCLIC(temp, ps[i & 3]);
+                       temp += B;
+                       A = D;
+                       D = C;
+                       C = B;
+                       B = temp;
+               }
+
+               ps += 4;
+               for (i = 0; i < 16; i++) {
+                       temp = A + FG(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+                       CYCLIC(temp, ps[i & 3]);
+                       temp += B;
+                       A = D;
+                       D = C;
+                       C = B;
+                       B = temp;
+               }
+               ps += 4;
+               for (i = 0; i < 16; i++) {
+                       temp = A + FH(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+                       CYCLIC(temp, ps[i & 3]);
+                       temp += B;
+                       A = D;
+                       D = C;
+                       C = B;
+                       B = temp;
+               }
+               ps += 4;
+               for (i = 0; i < 16; i++) {
+                       temp = A + FI(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+                       CYCLIC(temp, ps[i & 3]);
+                       temp += B;
+                       A = D;
+                       D = C;
+                       C = B;
+                       B = temp;
+               }
+
+#  endif       /* MD5_SIZE_VS_SPEED > 2 */
+# else
+               /* First round: using the given function, the context and a constant
+                  the next context is computed.  Because the algorithms processing
+                  unit is a 32-bit word and it is determined to work on words in
+                  little endian byte order we perhaps have to change the byte order
+                  before the computation.  To reduce the work for the next steps
+                  we store the swapped words in the array CORRECT_WORDS.  */
+
+#  define OP(a, b, c, d, s, T) \
+      do       \
+       {       \
+         a += FF (b, c, d) + (*cwp++ = SWAP_LE32(*words)) + T; \
+         ++words;      \
+         CYCLIC (a, s);        \
+         a += b;       \
+       }       \
+      while (0)
+
+               /* It is unfortunate that C does not provide an operator for
+                  cyclic rotation.  Hope the C compiler is smart enough.  */
+               /* gcc 2.95.4 seems to be --aaronl */
+#  define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
+
+               /* Before we start, one word to the strange constants.
+                  They are defined in RFC 1321 as
+
+                  T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
+                */
+
+#  if MD5_SIZE_VS_SPEED == 1
+               const uint32_t *pc;
+               const char *pp;
+               int i;
+#  endif       /* MD5_SIZE_VS_SPEED */
+
+               /* Round 1.  */
+#  if MD5_SIZE_VS_SPEED == 1
+               pc = C_array;
+               for (i = 0; i < 4; i++) {
+                       OP(A, B, C, D, 7, *pc++);
+                       OP(D, A, B, C, 12, *pc++);
+                       OP(C, D, A, B, 17, *pc++);
+                       OP(B, C, D, A, 22, *pc++);
+               }
+#  else
+               OP(A, B, C, D, 7, 0xd76aa478);
+               OP(D, A, B, C, 12, 0xe8c7b756);
+               OP(C, D, A, B, 17, 0x242070db);
+               OP(B, C, D, A, 22, 0xc1bdceee);
+               OP(A, B, C, D, 7, 0xf57c0faf);
+               OP(D, A, B, C, 12, 0x4787c62a);
+               OP(C, D, A, B, 17, 0xa8304613);
+               OP(B, C, D, A, 22, 0xfd469501);
+               OP(A, B, C, D, 7, 0x698098d8);
+               OP(D, A, B, C, 12, 0x8b44f7af);
+               OP(C, D, A, B, 17, 0xffff5bb1);
+               OP(B, C, D, A, 22, 0x895cd7be);
+               OP(A, B, C, D, 7, 0x6b901122);
+               OP(D, A, B, C, 12, 0xfd987193);
+               OP(C, D, A, B, 17, 0xa679438e);
+               OP(B, C, D, A, 22, 0x49b40821);
+#  endif       /* MD5_SIZE_VS_SPEED == 1 */
+
+               /* For the second to fourth round we have the possibly swapped words
+                  in CORRECT_WORDS.  Redefine the macro to take an additional first
+                  argument specifying the function to use.  */
+#  undef OP
+#  define OP(f, a, b, c, d, k, s, T)   \
+      do       \
+       {       \
+         a += f (b, c, d) + correct_words[k] + T;      \
+         CYCLIC (a, s);        \
+         a += b;       \
+       }       \
+      while (0)
+
+               /* Round 2.  */
+#  if MD5_SIZE_VS_SPEED == 1
+               pp = P_array;
+               for (i = 0; i < 4; i++) {
+                       OP(FG, A, B, C, D, (int) (*pp++), 5, *pc++);
+                       OP(FG, D, A, B, C, (int) (*pp++), 9, *pc++);
+                       OP(FG, C, D, A, B, (int) (*pp++), 14, *pc++);
+                       OP(FG, B, C, D, A, (int) (*pp++), 20, *pc++);
+               }
+#  else
+               OP(FG, A, B, C, D, 1, 5, 0xf61e2562);
+               OP(FG, D, A, B, C, 6, 9, 0xc040b340);
+               OP(FG, C, D, A, B, 11, 14, 0x265e5a51);
+               OP(FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
+               OP(FG, A, B, C, D, 5, 5, 0xd62f105d);
+               OP(FG, D, A, B, C, 10, 9, 0x02441453);
+               OP(FG, C, D, A, B, 15, 14, 0xd8a1e681);
+               OP(FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
+               OP(FG, A, B, C, D, 9, 5, 0x21e1cde6);
+               OP(FG, D, A, B, C, 14, 9, 0xc33707d6);
+               OP(FG, C, D, A, B, 3, 14, 0xf4d50d87);
+               OP(FG, B, C, D, A, 8, 20, 0x455a14ed);
+               OP(FG, A, B, C, D, 13, 5, 0xa9e3e905);
+               OP(FG, D, A, B, C, 2, 9, 0xfcefa3f8);
+               OP(FG, C, D, A, B, 7, 14, 0x676f02d9);
+               OP(FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
+#  endif       /* MD5_SIZE_VS_SPEED == 1 */
+
+               /* Round 3.  */
+#  if MD5_SIZE_VS_SPEED == 1
+               for (i = 0; i < 4; i++) {
+                       OP(FH, A, B, C, D, (int) (*pp++), 4, *pc++);
+                       OP(FH, D, A, B, C, (int) (*pp++), 11, *pc++);
+                       OP(FH, C, D, A, B, (int) (*pp++), 16, *pc++);
+                       OP(FH, B, C, D, A, (int) (*pp++), 23, *pc++);
+               }
+#  else
+               OP(FH, A, B, C, D, 5, 4, 0xfffa3942);
+               OP(FH, D, A, B, C, 8, 11, 0x8771f681);
+               OP(FH, C, D, A, B, 11, 16, 0x6d9d6122);
+               OP(FH, B, C, D, A, 14, 23, 0xfde5380c);
+               OP(FH, A, B, C, D, 1, 4, 0xa4beea44);
+               OP(FH, D, A, B, C, 4, 11, 0x4bdecfa9);
+               OP(FH, C, D, A, B, 7, 16, 0xf6bb4b60);
+               OP(FH, B, C, D, A, 10, 23, 0xbebfbc70);
+               OP(FH, A, B, C, D, 13, 4, 0x289b7ec6);
+               OP(FH, D, A, B, C, 0, 11, 0xeaa127fa);
+               OP(FH, C, D, A, B, 3, 16, 0xd4ef3085);
+               OP(FH, B, C, D, A, 6, 23, 0x04881d05);
+               OP(FH, A, B, C, D, 9, 4, 0xd9d4d039);
+               OP(FH, D, A, B, C, 12, 11, 0xe6db99e5);
+               OP(FH, C, D, A, B, 15, 16, 0x1fa27cf8);
+               OP(FH, B, C, D, A, 2, 23, 0xc4ac5665);
+#  endif       /* MD5_SIZE_VS_SPEED == 1 */
+
+               /* Round 4.  */
+#  if MD5_SIZE_VS_SPEED == 1
+               for (i = 0; i < 4; i++) {
+                       OP(FI, A, B, C, D, (int) (*pp++), 6, *pc++);
+                       OP(FI, D, A, B, C, (int) (*pp++), 10, *pc++);
+                       OP(FI, C, D, A, B, (int) (*pp++), 15, *pc++);
+                       OP(FI, B, C, D, A, (int) (*pp++), 21, *pc++);
+               }
+#  else
+               OP(FI, A, B, C, D, 0, 6, 0xf4292244);
+               OP(FI, D, A, B, C, 7, 10, 0x432aff97);
+               OP(FI, C, D, A, B, 14, 15, 0xab9423a7);
+               OP(FI, B, C, D, A, 5, 21, 0xfc93a039);
+               OP(FI, A, B, C, D, 12, 6, 0x655b59c3);
+               OP(FI, D, A, B, C, 3, 10, 0x8f0ccc92);
+               OP(FI, C, D, A, B, 10, 15, 0xffeff47d);
+               OP(FI, B, C, D, A, 1, 21, 0x85845dd1);
+               OP(FI, A, B, C, D, 8, 6, 0x6fa87e4f);
+               OP(FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
+               OP(FI, C, D, A, B, 6, 15, 0xa3014314);
+               OP(FI, B, C, D, A, 13, 21, 0x4e0811a1);
+               OP(FI, A, B, C, D, 4, 6, 0xf7537e82);
+               OP(FI, D, A, B, C, 11, 10, 0xbd3af235);
+               OP(FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
+               OP(FI, B, C, D, A, 9, 21, 0xeb86d391);
+#  endif       /* MD5_SIZE_VS_SPEED == 1 */
+# endif        /* MD5_SIZE_VS_SPEED > 1 */
+
+               /* Add the starting values of the context.  */
+               A += A_save;
+               B += B_save;
+               C += C_save;
+               D += D_save;
+
+       /* Put checksum in context given as argument.  */
+       ctx->A = A;
+       ctx->B = B;
+       ctx->C = C;
+       ctx->D = D;
+}
+
+/* Feed data through a temporary buffer to call md5_hash_aligned_block()
+ * with chunks of data that are 4-byte aligned and a multiple of 64 bytes.
+ * This function's internal buffer remembers previous data until it has 64
+ * bytes worth to pass on.  Call md5_end() to flush this buffer. */
+
+void md5_hash(const void *buffer, size_t len, md5_ctx_t *ctx)
+{
+       char *buf=(char *)buffer;
+
+       /* RFC 1321 specifies the possible length of the file up to 2^64 bits,
+        * Here we only track the number of bytes.  */
+
+       ctx->total += len;
+
+       // Process all input.
+
+       while (len) {
+               int i = 64 - ctx->buflen;
+
+               // Copy data into aligned buffer.
+
+               if (i > len) i = len;
+               memcpy(ctx->buffer + ctx->buflen, buf, i);
+               len -= i;
+               ctx->buflen += i;
+               buf += i;
+
+               // When buffer fills up, process it.
+
+               if (ctx->buflen == 64) {
+                       md5_hash_block(ctx->buffer, ctx);
+                       ctx->buflen = 0;
+               }
+       }
+}
+
+/* Process the remaining bytes in the buffer and put result from CTX
+ * in first 16 bytes following RESBUF.  The result is always in little
+ * endian byte order, so that a byte-wise output yields to the wanted
+ * ASCII representation of the message digest.
+ *
+ * IMPORTANT: On some systems it is required that RESBUF is correctly
+ * aligned for a 32 bits value.
+ */
+void *md5_end(void *resbuf, md5_ctx_t *ctx)
+{
+       char *buf = ctx->buffer;
+       int i;
+
+       /* Pad data to block size.  */
+
+       buf[ctx->buflen++] = 0x80;
+       memset(buf + ctx->buflen, 0, 128 - ctx->buflen);
+
+       /* Put the 64-bit file length in *bits* at the end of the buffer.  */
+       ctx->total <<= 3;
+       if (ctx->buflen > 56) buf += 64;
+       for (i = 0; i < 8; i++)  buf[56 + i] = ctx->total >> (i*8);
+
+       /* Process last bytes.  */
+       if (buf != ctx->buffer) md5_hash_block(ctx->buffer, ctx);
+       md5_hash_block(buf, ctx);
+
+       /* Put result from CTX in first 16 bytes following RESBUF.  The result is
+        * always in little endian byte order, so that a byte-wise output yields
+        * to the wanted ASCII representation of the message digest.
+        *
+        * IMPORTANT: On some systems it is required that RESBUF is correctly
+        * aligned for a 32 bits value.
+        */
+       ((uint32_t *) resbuf)[0] = SWAP_LE32(ctx->A);
+       ((uint32_t *) resbuf)[1] = SWAP_LE32(ctx->B);
+       ((uint32_t *) resbuf)[2] = SWAP_LE32(ctx->C);
+       ((uint32_t *) resbuf)[3] = SWAP_LE32(ctx->D);
+
+       return resbuf;
+}
+
diff --git a/libbb/messages.c b/libbb/messages.c
new file mode 100644 (file)
index 0000000..74a070c
--- /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] __attribute__(( aligned(sizeof(long long)) ));
diff --git a/libbb/mode_string.c b/libbb/mode_string.c
new file mode 100644 (file)
index 0000000..d17cc4a
--- /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 *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 *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..18386ef
--- /dev/null
@@ -0,0 +1,52 @@
+/* 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 erase_mtab(const char *name)
+{
+       struct mntent *entries = NULL;
+       int i, count = 0;
+       FILE *mountTable;
+       struct mntent *m;
+
+       mountTable = setmntent(bb_path_mtab_file, "r");
+       /* Bummer. Fall back on trying the /proc filesystem */
+       if (!mountTable) mountTable = setmntent("/proc/mounts", "r");
+       if (!mountTable) {
+               bb_perror_msg(bb_path_mtab_file);
+               return;
+       }
+
+       while ((m = getmntent(mountTable)) != 0) {
+               i = count++;
+               entries = xrealloc(entries, count * sizeof(entries[0]));
+               entries[i].mnt_fsname = xstrdup(m->mnt_fsname);
+               entries[i].mnt_dir = xstrdup(m->mnt_dir);
+               entries[i].mnt_type = xstrdup(m->mnt_type);
+               entries[i].mnt_opts = xstrdup(m->mnt_opts);
+               entries[i].mnt_freq = m->mnt_freq;
+               entries[i].mnt_passno = m->mnt_passno;
+       }
+       endmntent(mountTable);
+
+       mountTable = setmntent(bb_path_mtab_file, "w");
+       if (mountTable) {
+               for (i = 0; i < count; i++) {
+                       if (strcmp(entries[i].mnt_fsname, name) != 0
+                        && strcmp(entries[i].mnt_dir, name) != 0)
+                               addmntent(mountTable, &entries[i]);
+               }
+               endmntent(mountTable);
+       } else if (errno != EROFS)
+               bb_perror_msg(bb_path_mtab_file);
+}
+#endif
diff --git a/libbb/mtab_file.c b/libbb/mtab_file.c
new file mode 100644 (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..1841b27
--- /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 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_mode.c b/libbb/parse_mode.c
new file mode 100644 (file)
index 0000000..fd54900
--- /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 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..af9ff59
--- /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 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 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..7b50073
--- /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 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 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..62ce888
--- /dev/null
@@ -0,0 +1,21 @@
+/* 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"
+extern void bb_perror_msg(const char *s, ...);
+
+/* suppress gcc "no previous prototype" warning */
+void bb_perror_nomsg(void);
+void 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..dab3df6
--- /dev/null
@@ -0,0 +1,21 @@
+/* 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"
+extern void bb_perror_msg_and_die(const char *s, ...);
+
+/* suppress gcc "no previous prototype" warning */
+void bb_perror_nomsg_and_die(void);
+void 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..cafa789
--- /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 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/printable.c b/libbb/printable.c
new file mode 100644 (file)
index 0000000..676758a
--- /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 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..1cadbd3
--- /dev/null
@@ -0,0 +1,86 @@
+/* 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 bb_process_escape_sequence(const char **ptr)
+{
+       static const char charmap[] ALIGN1 = {
+               '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;
+}
diff --git a/libbb/procps.c b/libbb/procps.c
new file mode 100644 (file)
index 0000000..8946917
--- /dev/null
@@ -0,0 +1,458 @@
+/* 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 {
+       unsigned 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 clear_username_cache(void)
+{
+       clear_cache(&username);
+       clear_cache(&groupname);
+}
+
+#if 0 /* more generic, but we don't need that yet */
+/* Returns -N-1 if not found. */
+/* cp->cache[N] is allocated and must be filled in this case */
+static int get_cached(cache_t *cp, unsigned id)
+{
+       int i;
+       for (i = 0; i < cp->size; i++)
+               if (cp->cache[i].id == id)
+                       return i;
+       i = cp->size++;
+       cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache));
+       cp->cache[i++].id = id;
+       return -i;
+}
+#endif
+
+typedef char* ug_func(char *name, int bufsize, long uid);
+static char* get_cached(cache_t *cp, unsigned id, ug_func* fp)
+{
+       int i;
+       for (i = 0; i < cp->size; i++)
+               if (cp->cache[i].id == id)
+                       return cp->cache[i].name;
+       i = cp->size++;
+       cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache));
+       cp->cache[i].id = id;
+       /* Never fails. Generates numeric string if name isn't found */
+       fp(cp->cache[i].name, sizeof(cp->cache[i].name), id);
+       return cp->cache[i].name;
+}
+const char* get_cached_username(uid_t uid)
+{
+       return get_cached(&username, uid, bb_getpwuid);
+}
+const char* get_cached_groupname(gid_t gid)
+{
+       return get_cached(&groupname, gid, bb_getgrgid);
+}
+
+
+#define PROCPS_BUFSIZE 1024
+
+static int read_to_buf(const char *filename, void *buf)
+{
+       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 *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 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 *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 */
+                       /*      "%lu %lu %lu %lu %lu %lu " rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */
+                       /*      "%u %u %u %u "         signal, blocked, sigignore, sigcatch */
+                       /*      "%lu %lu %lu"          wchan, nswap, cnswap */
+                               ,
+                               sp->state, &sp->ppid,
+                               &sp->pgid, &sp->sid, &tty,
+                               &sp->utime, &sp->stime,
+                               &tasknice,
+                               &sp->start_time,
+                               &vsz,
+                               &rss);
+                       if (n != 11)
+                               break;
+                       /* 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;
+#endif
+
+                       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(filename, "r");
+                       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 ENABLE_PGREP || ENABLE_PKILL
+                       if (flags & PSSCAN_ARGVN) {
+                               do {
+                                       n--;
+                                       if (buf[n] == '\0')
+                                               buf[n] = ' ';
+                               } while (n);
+                       }
+#endif
+                       sp->argv0 = xstrdup(buf);
+               }
+#endif
+               break;
+       }
+       return sp;
+}
+
+void 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..f8ccbf1
--- /dev/null
@@ -0,0 +1,11 @@
+/* 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.
+ */
+
+/* We cheat here. It is declared as const ptr in libbb.h,
+ * but here we make it live in R/W memory */
+struct globals;
+struct globals *ptr_to_globals;
diff --git a/libbb/pw_encrypt.c b/libbb/pw_encrypt.c
new file mode 100644 (file)
index 0000000..e9cf4e3
--- /dev/null
@@ -0,0 +1,27 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routine.
+ *
+ * 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 <crypt.h>
+
+char *pw_encrypt(const char *clear, const char *salt)
+{
+       /* Was static char[BIGNUM]. Malloced thing works as well */
+       static char *cipher;
+
+#if 0 /* was CONFIG_FEATURE_SHA1_PASSWORDS, but there is no such thing??? */
+       if (strncmp(salt, "$2$", 3) == 0) {
+               return sha1_crypt(clear);
+       }
+#endif
+
+       free(cipher);
+       cipher = xstrdup(crypt(clear, salt));
+       return cipher;
+}
diff --git a/libbb/read.c b/libbb/read.c
new file mode 100644 (file)
index 0000000..5754465
--- /dev/null
@@ -0,0 +1,227 @@
+/* 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 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 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 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 xread(int fd, void *buf, size_t count)
+{
+       if (count) {
+               ssize_t size = full_read(fd, buf, count);
+               if (size != count)
+                       bb_error_msg_and_die("short read");
+       }
+}
+
+// Die with an error message if we can't read one character.
+unsigned char xread_char(int fd)
+{
+       char tmp;
+       xread(fd, &tmp, 1);
+       return tmp;
+}
+
+// Read one line a-la fgets. Works only on seekable streams
+char *reads(int fd, char *buffer, size_t size)
+{
+       char *p;
+
+       if (size < 2)
+               return NULL;
+       size = full_read(fd, buffer, size-1);
+       if ((ssize_t)size <= 0)
+               return NULL;
+
+       buffer[size] = '\0';
+       p = strchr(buffer, '\n');
+       if (p) {
+               off_t offset;
+               *p++ = '\0';
+               // avoid incorrect (unsigned) widening
+               offset = (off_t)(p-buffer) - (off_t)size;
+               // set fd position right after '\n'
+               if (offset && lseek(fd, offset, SEEK_CUR) == (off_t)-1)
+                       return NULL;
+       }
+       return buffer;
+}
+
+// Read one line a-la fgets. 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 *xmalloc_reads(int fd, char *buf)
+{
+       char *p;
+       int sz = buf ? strlen(buf) : 0;
+
+       goto jump_in;
+       while (1) {
+               if (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';
+       return xrealloc(buf, p - buf);
+}
+
+ssize_t read_close(int fd, void *buf, size_t size)
+{
+       /*int e;*/
+       size = full_read(fd, buf, size);
+       /*e = errno;*/
+       close(fd);
+       /*errno = e;*/
+       return size;
+}
+
+ssize_t open_read_close(const char *filename, void *buf, size_t size)
+{
+       int fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               return fd;
+       return read_close(fd, buf, size);
+}
+
+// Read (potentially big) files in one go. File size is estimated by
+// lseek to end.
+void *xmalloc_open_read_close(const char *filename, size_t *sizep)
+{
+       char *buf;
+       size_t size = sizep ? *sizep : INT_MAX;
+       int fd;
+       off_t len;
+
+       fd = xopen(filename, O_RDONLY);
+       /* /proc/N/stat files report len 0 here */
+       /* In order to make such files readable, we add small const */
+       len = xlseek(fd, 0, SEEK_END) | 0x3ff; /* + up to 1k */
+       xlseek(fd, 0, SEEK_SET);
+       if (len < size)
+               size = len;
+       buf = xmalloc(size + 1);
+       size = read_close(fd, buf, size);
+       if ((ssize_t)size < 0)
+               bb_perror_msg_and_die("'%s'", filename);
+       xrealloc(buf, size + 1);
+       buf[size] = '\0';
+       if (sizep)
+               *sizep = size;
+       return buf;
+}
diff --git a/libbb/recursive_action.c b/libbb/recursive_action.c
new file mode 100644 (file)
index 0000000..513aff3
--- /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"
+
+#undef DEBUG_RECURS_ACTION
+
+/*
+ * Walk down all the directories under the specified
+ * location, and do something (something specified
+ * by the fileAction and dirAction function pointers).
+ *
+ * Unfortunately, while nftw(3) could replace this and reduce
+ * code size a bit, nftw() wasn't supported before GNU libc 2.1,
+ * and so isn't sufficiently portable to take over since glibc2.1
+ * is so stinking huge.
+ */
+
+static int true_action(const char *fileName ATTRIBUTE_UNUSED,
+               struct stat *statbuf ATTRIBUTE_UNUSED,
+               void* userData ATTRIBUTE_UNUSED,
+               int depth ATTRIBUTE_UNUSED)
+{
+       return TRUE;
+}
+
+/* fileAction return value of 0 on any file in directory will make
+ * recursive_action() return 0, but it doesn't stop directory traversal
+ * (fileAction/dirAction will be called on each file).
+ *
+ * if !depthFirst, dirAction return value of 0 (FALSE) or 2 (SKIP)
+ * prevents recursion into that directory, instead
+ * recursive_action() returns 0 (if FALSE) or 1 (if SKIP).
+ *
+ * followLinks=0/1 differs mainly in handling of links to dirs.
+ * 0: lstat(statbuf). Calls fileAction on link name even if points to dir.
+ * 1: stat(statbuf). Calls dirAction and optionally recurse on link to dir.
+ */
+
+int recursive_action(const char *fileName,
+               unsigned flags,
+               int (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
+               int (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth),
+               void* userData,
+               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;
+               /* now descend into it (NB: ACTION_RECURSE is set in flags) */
+               if (!recursive_action(nextFile, flags, fileAction, dirAction, userData, depth+1))
+                       status = FALSE;
+               free(nextFile);
+       }
+       closedir(dir);
+
+       if (flags & ACTION_DEPTHFIRST) {
+               if (!dirAction(fileName, &statbuf, userData, depth))
+                       goto done_nak_warn;
+       }
+
+       if (!status)
+               return FALSE;
+       return TRUE;
+
+ done_nak_warn:
+       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..3edc91d
--- /dev/null
@@ -0,0 +1,100 @@
+/* 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 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..dc4cfb4
--- /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 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..78f10c6
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Common RTC functions
+ */
+
+#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 rtc_adjtime_is_utc(void)
+{
+       int utc = 0;
+       FILE *f = fopen(ADJTIME_PATH, "r");
+
+       if (f) {
+               RESERVE_CONFIG_BUFFER(buffer, 128);
+
+               while (fgets(buffer, sizeof(buffer), f)) {
+                       int len = strlen(buffer);
+
+                       while (len && isspace(buffer[len - 1]))
+                               len--;
+
+                       buffer[len] = 0;
+
+                       if (strncmp(buffer, "UTC", 3) == 0) {
+                               utc = 1;
+                               break;
+                       }
+               }
+               fclose(f);
+
+               RELEASE_CONFIG_BUFFER(buffer);
+       }
+
+       return utc;
+}
+
+int 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 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..239887d
--- /dev/null
@@ -0,0 +1,92 @@
+/* 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
+renew_current_security_context(void)
+{
+       freecon(current_sid);  /* Release old context  */
+       getcon(&current_sid);  /* update */
+}
+void
+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 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..1290f4c
--- /dev/null
@@ -0,0 +1,53 @@
+/* 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 *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) ? "?" : uts.nodename, sizeof(uts.nodename));
+}
diff --git a/libbb/safe_poll.c b/libbb/safe_poll.c
new file mode 100644 (file)
index 0000000..d2b773c
--- /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 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..cc42583
--- /dev/null
@@ -0,0 +1,18 @@
+/* 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 *safe_strncpy(char *dst, const char *src, size_t size)
+{
+       if (!size) return dst;
+       dst[--size] = '\0';
+       return strncpy(dst, src, size);
+}
diff --git a/libbb/safe_write.c b/libbb/safe_write.c
new file mode 100644 (file)
index 0000000..5bbb82e
--- /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 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..7478cc7
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * libbb/selinux_common.c
+ *   -- common SELinux utility functions
+ *
+ * Copyright 2007 KaiGai Kohei <kaigai@kaigai.gr.jp>
+ */
+#include "libbb.h"
+#include <selinux/context.h>
+
+context_t 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 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 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..6e3575c
--- /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 setup_environment(const char *shell, int clear_env, int change_env, const struct passwd *pw)
+{
+       if (clear_env) {
+               const char *term;
+
+               /* 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);
+               }
+
+               /* 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..552dcad
--- /dev/null
@@ -0,0 +1,170 @@
+/* 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
+ */
+
+#include "libbb.h"
+
+#define SHA1_BLOCK_SIZE  64
+#define SHA1_DIGEST_SIZE 20
+#define SHA1_HASH_SIZE   SHA1_DIGEST_SIZE
+#define SHA2_GOOD        0
+#define SHA2_BAD         1
+
+#define rotl32(x,n)      (((x) << n) | ((x) >> (32 - n)))
+
+#define SHA1_MASK        (SHA1_BLOCK_SIZE - 1)
+
+/* reverse byte order in 32-bit words   */
+#define ch(x,y,z)        ((z) ^ ((x) & ((y) ^ (z))))
+#define parity(x,y,z)    ((x) ^ (y) ^ (z))
+#define maj(x,y,z)       (((x) & (y)) | ((z) & ((x) | (y))))
+
+/* A normal version as set out in the FIPS. This version uses   */
+/* partial loop unrolling and is optimised for the Pentium 4    */
+#define rnd(f,k) \
+       do { \
+               t = a; a = rotl32(a,5) + f(b,c,d) + e + k + w[i]; \
+               e = d; d = c; c = rotl32(b, 30); b = t; \
+       } while (0)
+
+static void sha1_compile(sha1_ctx_t *ctx)
+{
+       uint32_t w[80], i, a, b, c, d, e, t;
+
+       /* note that words are compiled from the buffer into 32-bit */
+       /* words in big-endian order so an order reversal is needed */
+       /* here on little endian machines                           */
+       for (i = 0; i < SHA1_BLOCK_SIZE / 4; ++i)
+               w[i] = htonl(ctx->wbuf[i]);
+
+       for (i = SHA1_BLOCK_SIZE / 4; i < 80; ++i)
+               w[i] = rotl32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
+
+       a = ctx->hash[0];
+       b = ctx->hash[1];
+       c = ctx->hash[2];
+       d = ctx->hash[3];
+       e = ctx->hash[4];
+
+       for (i = 0; i < 20; ++i) {
+               rnd(ch, 0x5a827999);
+       }
+
+       for (i = 20; i < 40; ++i) {
+               rnd(parity, 0x6ed9eba1);
+       }
+
+       for (i = 40; i < 60; ++i) {
+               rnd(maj, 0x8f1bbcdc);
+       }
+
+       for (i = 60; i < 80; ++i) {
+               rnd(parity, 0xca62c1d6);
+       }
+
+       ctx->hash[0] += a;
+       ctx->hash[1] += b;
+       ctx->hash[2] += c;
+       ctx->hash[3] += d;
+       ctx->hash[4] += e;
+}
+
+void sha1_begin(sha1_ctx_t *ctx)
+{
+       ctx->count[0] = ctx->count[1] = 0;
+       ctx->hash[0] = 0x67452301;
+       ctx->hash[1] = 0xefcdab89;
+       ctx->hash[2] = 0x98badcfe;
+       ctx->hash[3] = 0x10325476;
+       ctx->hash[4] = 0xc3d2e1f0;
+}
+
+/* SHA1 hash data in an array of bytes into hash buffer and call the        */
+/* hash_compile function as required.                                       */
+void sha1_hash(const void *data, size_t length, sha1_ctx_t *ctx)
+{
+       uint32_t pos = (uint32_t) (ctx->count[0] & SHA1_MASK);
+       uint32_t freeb = SHA1_BLOCK_SIZE - pos;
+       const unsigned char *sp = data;
+
+       if ((ctx->count[0] += length) < length)
+               ++(ctx->count[1]);
+
+       while (length >= freeb) {       /* tranfer whole blocks while possible  */
+               memcpy(((unsigned char *) ctx->wbuf) + pos, sp, freeb);
+               sp += freeb;
+               length -= freeb;
+               freeb = SHA1_BLOCK_SIZE;
+               pos = 0;
+               sha1_compile(ctx);
+       }
+
+       memcpy(((unsigned char *) ctx->wbuf) + pos, sp, length);
+}
+
+void *sha1_end(void *resbuf, sha1_ctx_t *ctx)
+{
+       /* SHA1 Final padding and digest calculation  */
+#if BB_BIG_ENDIAN
+       static uint32_t mask[4] = { 0x00000000, 0xff000000, 0xffff0000, 0xffffff00 };
+       static uint32_t bits[4] = { 0x80000000, 0x00800000, 0x00008000, 0x00000080 };
+#else
+       static uint32_t mask[4] = { 0x00000000, 0x000000ff, 0x0000ffff, 0x00ffffff };
+       static uint32_t bits[4] = { 0x00000080, 0x00008000, 0x00800000, 0x80000000 };
+#endif
+
+       uint8_t *hval = resbuf;
+       uint32_t i, cnt = (uint32_t) (ctx->count[0] & SHA1_MASK);
+
+       /* mask out the rest of any partial 32-bit word and then set    */
+       /* the next byte to 0x80. On big-endian machines any bytes in   */
+       /* the buffer will be at the top end of 32 bit words, on little */
+       /* endian machines they will be at the bottom. Hence the AND    */
+       /* and OR masks above are reversed for little endian systems    */
+       ctx->wbuf[cnt >> 2] =
+               (ctx->wbuf[cnt >> 2] & mask[cnt & 3]) | bits[cnt & 3];
+
+       /* we need 9 or more empty positions, one for the padding byte  */
+       /* (above) and eight for the length count.  If there is not     */
+       /* enough space pad and empty the buffer                        */
+       if (cnt > SHA1_BLOCK_SIZE - 9) {
+               if (cnt < 60)
+                       ctx->wbuf[15] = 0;
+               sha1_compile(ctx);
+               cnt = 0;
+       } else                          /* compute a word index for the empty buffer positions  */
+               cnt = (cnt >> 2) + 1;
+
+       while (cnt < 14)        /* and zero pad all but last two positions      */
+               ctx->wbuf[cnt++] = 0;
+
+       /* assemble the eight byte counter in the buffer in big-endian  */
+       /* format                                                       */
+
+       ctx->wbuf[14] = htonl((ctx->count[1] << 3) | (ctx->count[0] >> 29));
+       ctx->wbuf[15] = htonl(ctx->count[0] << 3);
+
+       sha1_compile(ctx);
+
+       /* extract the hash value as bytes in case the hash buffer is   */
+       /* misaligned for 32-bit words                                  */
+
+       for (i = 0; i < SHA1_DIGEST_SIZE; ++i)
+               hval[i] = (unsigned char) (ctx->hash[i >> 2] >> 8 * (~i & 3));
+
+       return resbuf;
+}
diff --git a/libbb/signals.c b/libbb/signals.c
new file mode 100644 (file)
index 0000000..685c552
--- /dev/null
@@ -0,0 +1,113 @@
+/* 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"
+
+/* Saves 2 bytes on x86! Oh my... */
+int sigaction_set(int signum, const struct sigaction *act)
+{
+       return sigaction(signum, act, NULL);
+}
+
+int sigprocmask_allsigs(int how)
+{
+       sigset_t set;
+       sigfillset(&set);
+       return sigprocmask(how, &set, NULL);
+}
+
+void 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 bb_signals_recursive(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 sig_block(int sig)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigaddset(&ss, sig);
+       sigprocmask(SIG_BLOCK, &ss, NULL);
+}
+
+void sig_unblock(int sig)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigaddset(&ss, sig);
+       sigprocmask(SIG_UNBLOCK, &ss, NULL);
+}
+
+void wait_for_any_sig(void)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigsuspend(&ss);
+}
+
+/* Assuming the sig is fatal */
+void kill_myself_with_sig(int sig)
+{
+       signal(sig, SIG_DFL);
+       sig_unblock(sig);
+       raise(sig);
+       _exit(1); /* Should not reach it */
+}
+
+void 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 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..29e371d
--- /dev/null
@@ -0,0 +1,53 @@
+/* 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 *bb_simplify_path(const char *path)
+{
+       char *s, *start, *p;
+
+       if (path[0] == '/')
+               start = xstrdup(path);
+       else {
+               s = xrealloc_getcwd_or_warn(NULL);
+               start = concat_path_file(s, path);
+               free(s);
+       }
+       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 start;
+}
diff --git a/libbb/skip_whitespace.c b/libbb/skip_whitespace.c
new file mode 100644 (file)
index 0000000..87b5f23
--- /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 *skip_whitespace(const char *s)
+{
+       /* NB: isspace('\0') returns 0 */
+       while (isspace(*s)) ++s;
+
+       return (char *) s;
+}
+
+char *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..94a2962
--- /dev/null
@@ -0,0 +1,117 @@
+/* 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 <termios.h>
+#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 int tty_baud_to_value(speed_t speed)
+{
+       int i = 0;
+
+       do {
+               if (speed == speeds[i].speed) {
+                       if (speeds[i].value & 0x8000U) {
+                               return ((unsigned long) (speeds[i].value) & 0x7fffU) * 256;
+                       }
+                       return speeds[i].value;
+               }
+       } while (++i < NUM_SPEEDS);
+
+       return 0;
+}
+
+speed_t tty_value_to_baud(unsigned int value)
+{
+       int i = 0;
+
+       do {
+               if (value == tty_baud_to_value(speeds[i].speed)) {
+                       return speeds[i].speed;
+               }
+       } while (++i < NUM_SPEEDS);
+
+       return (speed_t) - 1;
+}
+
+#if 0
+/* testing code */
+#include <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..037f717
--- /dev/null
@@ -0,0 +1,13 @@
+/* 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* str_tolower(char *str)
+{
+       char *c;
+       for (c = str; *c; ++c)
+               *c = tolower(*c);
+       return str;
+}
diff --git a/libbb/time.c b/libbb/time.c
new file mode 100644 (file)
index 0000000..3aa0ee3
--- /dev/null
@@ -0,0 +1,43 @@
+/* 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>
+
+/* libc has incredibly messy way of doing this,
+ * typically requiring -lrt. We just skip all this mess */
+unsigned long long monotonic_us(void)
+{
+       struct timespec ts;
+       if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &ts))
+               bb_error_msg_and_die("clock_gettime(MONOTONIC) failed");
+       return ts.tv_sec * 1000000ULL + ts.tv_nsec/1000;
+}
+unsigned monotonic_sec(void)
+{
+       struct timespec ts;
+       if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &ts))
+               bb_error_msg_and_die("clock_gettime(MONOTONIC) failed");
+       return ts.tv_sec;
+}
+#else
+unsigned long long monotonic_us(void)
+{
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       return tv.tv_sec * 1000000ULL + tv.tv_usec;
+}
+
+unsigned monotonic_sec(void)
+{
+       return time(NULL);
+}
+#endif
diff --git a/libbb/trim.c b/libbb/trim.c
new file mode 100644 (file)
index 0000000..94ccaf7
--- /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 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..97e9949
--- /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 get_signum(const char *name)
+{
+       int 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 *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 print_signames(void)
+{
+       int 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..e968ecb
--- /dev/null
@@ -0,0 +1,163 @@
+/* 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
+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
+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;
+       char cbuf[sizeof(struct in_pktinfo)
+#if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
+               | sizeof(struct in6_pktinfo) /* (a|b) is poor man's max(a,b) */
+#endif
+       ];
+       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(cbuf, 0, sizeof(cbuf));
+
+       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 = cbuf;
+       msg.msg_controllen = sizeof(cbuf);
+       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
+       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
+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))];
+               char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+       } u;
+       struct cmsghdr *cmsgptr;
+       struct msghdr msg;
+       socklen_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..d10e863
--- /dev/null
@@ -0,0 +1,153 @@
+/* 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>
+ */
+
+#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
+
+int update_passwd(const char *filename, const char *username,
+                       const char *new_pw)
+{
+       struct stat sb;
+       struct flock lock;
+       FILE *old_fp;
+       FILE *new_fp;
+       char *fnamesfx;
+       char *sfx_char;
+       unsigned user_len;
+       int old_fd;
+       int new_fd;
+       int i;
+       int cnt = 0;
+       int ret = -1; /* failure */
+
+       filename = xmalloc_follow_symlinks(filename);
+       if (filename == NULL)
+               return -1;
+
+       check_selinux_update_passwd(username);
+
+       /* New passwd file, "/etc/passwd+" for now */
+       fnamesfx = xasprintf("%s+", filename);
+       sfx_char = &fnamesfx[strlen(fnamesfx)-1];
+       username = xasprintf("%s:", username);
+       user_len = strlen(username);
+
+       old_fp = fopen(filename, "r+");
+       if (!old_fp)
+               goto free_mem;
+       old_fd = fileno(old_fp);
+
+       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("cannot 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);
+       }
+       new_fp = fdopen(new_fd, "w");
+       if (!new_fp) {
+               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: cannot 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: cannot lock '%s'", filename);
+       lock.l_type = F_UNLCK;
+
+       /* Read current password file, write updated /etc/passwd+ */
+       while (1) {
+               char *line = xmalloc_fgets(old_fp);
+               if (!line) break; /* EOF/error */
+               if (strncmp(username, line, user_len) == 0) {
+                       /* we have a match with "username:"... */
+                       const char *cp = line + user_len;
+                       /* now cp -> old passwd, skip it: */
+                       cp = strchrnul(cp, ':');
+                       /* now cp -> ':' after old passwd or -> "" */
+                       fprintf(new_fp, "%s%s%s", username, new_pw, cp);
+                       cnt++;
+               } else
+                       fputs(line, new_fp);
+               free(line);
+       }
+       fcntl(old_fd, F_SETLK, &lock);
+
+       /* We do want all of them to execute, thus | instead of || */
+       if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
+        || rename(fnamesfx, filename)
+       ) {
+               /* At least one of those failed */
+               goto unlink_new;
+       }
+       ret = cnt; /* whee, success! */
+
+ unlink_new:
+       if (ret < 0) unlink(fnamesfx);
+
+ close_old_fp:
+       fclose(old_fp);
+
+ free_mem:
+       free(fnamesfx);
+       free((char *)filename);
+       free((char *)username);
+       return ret;
+}
diff --git a/libbb/uuencode.c b/libbb/uuencode.c
new file mode 100644 (file)
index 0000000..0aedf33
--- /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 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..726d563
--- /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 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..ca44030
--- /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 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(2, 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 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..1567d89
--- /dev/null
@@ -0,0 +1,283 @@
+/* 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 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 xspawn(char **argv)
+{
+       pid_t pid = spawn(argv);
+       if (pid < 0)
+               bb_simple_perror_msg_and_die(*argv);
+       return pid;
+}
+
+int safe_waitpid(int pid, int *wstat, int options)
+{
+       int r;
+
+       do
+               r = waitpid(pid, wstat, options);
+       while ((r == -1) && (errno == EINTR));
+       return r;
+}
+
+int 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 wait4pid(int 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 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 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 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;
+
+       /* option_mask32 = 0; - not needed */
+
+       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);
+       } else { /* xfunc died in NOFORK applet */
+               /* in case they meant to return 0... */
+               if (rc == -2222)
+                       rc = 0;
+       }
+
+       /* Restoring globals */
+       restore_nofork_data(old);
+       return rc;
+}
+
+int 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 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 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);
+}
+
+void forkexit_or_rexec(char **argv)
+{
+       pid_t pid;
+       /* Maybe we are already re-execed and come here again? */
+       if (re_execed)
+               return;
+
+       pid = vfork();
+       if (pid < 0) /* wtf? */
+               bb_perror_msg_and_die("vfork");
+       if (pid) /* parent */
+               exit(0);
+       /* child - re-exec ourself */
+       re_exec(argv);
+}
+#else
+/* Dance around (void)...*/
+#undef forkexit_or_rexec
+void forkexit_or_rexec(void)
+{
+       pid_t pid;
+       pid = fork();
+       if (pid < 0) /* wtf? */
+               bb_perror_msg_and_die("fork");
+       if (pid) /* parent */
+               exit(0);
+       /* child */
+}
+#define forkexit_or_rexec(argv) forkexit_or_rexec()
+#endif
+
+/* Due to a #define in libbb.h on MMU systems we actually have 1 argument -
+ * char **argv "vanishes" */
+void 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 = xopen(bb_dev_null, O_RDWR);
+
+       while ((unsigned)fd < 2)
+               fd = dup(fd); /* have 0,1,2 open at least to /dev/null */
+
+       if (!(flags & DAEMON_ONLY_SANITIZE)) {
+               forkexit_or_rexec(argv);
+               /* 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 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..be78a44
--- /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 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..9248874
--- /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"
+
+FILE *fopen_or_warn(const char *path, const char *mode)
+{
+       FILE *fp = fopen(path, mode);
+       if (!fp) {
+               bb_simple_perror_msg(path);
+               errno = 0;
+       }
+       return fp;
+}
diff --git a/libbb/wfopen_input.c b/libbb/wfopen_input.c
new file mode 100644 (file)
index 0000000..a7c1c32
--- /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 *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 *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 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/xatonum.c b/libbb/xatonum.c
new file mode 100644 (file)
index 0000000..a410ae9
--- /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 xatoi_u(const char *numstr)
+{
+       return xatou_range(numstr, 0, INT_MAX);
+}
+
+uint16_t xatou16(const char *numstr)
+{
+       return xatou_range(numstr, 0, 0xffff);
+}
diff --git a/libbb/xatonum_template.c b/libbb/xatonum_template.c
new file mode 100644 (file)
index 0000000..9f9dc11
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+You need to define the following (example):
+
+#define type long
+#define xstrtou(rest) xstrtoul##rest
+#define xstrto(rest) xstrtol##rest
+#define xatou(rest) xatoul##rest
+#define xato(rest) xatol##rest
+#define XSTR_UTYPE_MAX ULONG_MAX
+#define XSTR_TYPE_MAX LONG_MAX
+#define XSTR_TYPE_MIN LONG_MIN
+#define XSTR_STRTOU strtoul
+*/
+
+unsigned type xstrtou(_range_sfx)(const char *numstr, int base,
+               unsigned type lower,
+               unsigned type upper,
+               const struct suffix_mult *suffixes)
+{
+       unsigned type r;
+       int old_errno;
+       char *e;
+
+       /* Disallow '-' and any leading whitespace. 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 xstrtou(_range)(const char *numstr, int base,
+               unsigned type lower,
+               unsigned type upper)
+{
+       return xstrtou(_range_sfx)(numstr, base, lower, upper, NULL);
+}
+
+unsigned type xstrtou(_sfx)(const char *numstr, int base,
+               const struct suffix_mult *suffixes)
+{
+       return xstrtou(_range_sfx)(numstr, base, 0, XSTR_UTYPE_MAX, suffixes);
+}
+
+unsigned type xstrtou()(const char *numstr, int base)
+{
+       return xstrtou(_range_sfx)(numstr, base, 0, XSTR_UTYPE_MAX, NULL);
+}
+
+unsigned type xatou(_range_sfx)(const char *numstr,
+               unsigned type lower,
+               unsigned type upper,
+               const struct suffix_mult *suffixes)
+{
+       return xstrtou(_range_sfx)(numstr, 10, lower, upper, suffixes);
+}
+
+unsigned type xatou(_range)(const char *numstr,
+               unsigned type lower,
+               unsigned type upper)
+{
+       return xstrtou(_range_sfx)(numstr, 10, lower, upper, NULL);
+}
+
+unsigned type xatou(_sfx)(const char *numstr,
+               const struct suffix_mult *suffixes)
+{
+       return xstrtou(_range_sfx)(numstr, 10, 0, XSTR_UTYPE_MAX, suffixes);
+}
+
+unsigned type xatou()(const char *numstr)
+{
+       return xatou(_sfx)(numstr, NULL);
+}
+
+/* Signed ones */
+
+type xstrto(_range_sfx)(const char *numstr, int base,
+               type lower,
+               type upper,
+               const struct suffix_mult *suffixes)
+{
+       unsigned type u = XSTR_TYPE_MAX;
+       type r;
+       const char *p = numstr;
+
+       /* 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 xstrto(_range)(const char *numstr, int base, type lower, type upper)
+{
+       return xstrto(_range_sfx)(numstr, base, lower, upper, NULL);
+}
+
+type xato(_range_sfx)(const char *numstr,
+               type lower,
+               type upper,
+               const struct suffix_mult *suffixes)
+{
+       return xstrto(_range_sfx)(numstr, 10, lower, upper, suffixes);
+}
+
+type xato(_range)(const char *numstr, type lower, type upper)
+{
+       return xstrto(_range_sfx)(numstr, 10, lower, upper, NULL);
+}
+
+type xato(_sfx)(const char *numstr, const struct suffix_mult *suffixes)
+{
+       return xstrto(_range_sfx)(numstr, 10, XSTR_TYPE_MIN, XSTR_TYPE_MAX, suffixes);
+}
+
+type xato()(const char *numstr)
+{
+       return xstrto(_range_sfx)(numstr, 10, XSTR_TYPE_MIN, XSTR_TYPE_MAX, NULL);
+}
+
+#undef type
+#undef xstrtou
+#undef xstrto
+#undef xatou
+#undef xato
+#undef XSTR_UTYPE_MAX
+#undef XSTR_TYPE_MAX
+#undef XSTR_TYPE_MIN
+#undef XSTR_STRTOU
diff --git a/libbb/xconnect.c b/libbb/xconnect.c
new file mode 100644 (file)
index 0000000..950aee8
--- /dev/null
@@ -0,0 +1,386 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Connect to host at port using address resolution from getaddrinfo
+ *
+ */
+
+#include <netinet/in.h>
+#include "libbb.h"
+
+void setsockopt_reuseaddr(int fd)
+{
+       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &const_int_1, sizeof(const_int_1));
+}
+int setsockopt_broadcast(int fd)
+{
+       return setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &const_int_1, sizeof(const_int_1));
+}
+
+void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen)
+{
+       if (connect(s, s_addr, addrlen) < 0) {
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       close(s);
+               if (s_addr->sa_family == AF_INET)
+                       bb_perror_msg_and_die("%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 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 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 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 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 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] != ':') { /* Malformed: must have [xx]:nn */
+                       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 ']' */
+               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;
+               }
+       }
+
+       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* host_and_af2sockaddr(const char *host, int port, sa_family_t af)
+{
+       return str2sockaddr(host, port, af, 0);
+}
+
+len_and_sockaddr* xhost_and_af2sockaddr(const char *host, int port, sa_family_t af)
+{
+       return str2sockaddr(host, port, af, DIE_ON_ERROR);
+}
+#endif
+
+len_and_sockaddr* host2sockaddr(const char *host, int port)
+{
+       return str2sockaddr(host, port, AF_UNSPEC, 0);
+}
+
+len_and_sockaddr* xhost2sockaddr(const char *host, int port)
+{
+       return str2sockaddr(host, port, AF_UNSPEC, DIE_ON_ERROR);
+}
+
+len_and_sockaddr* xdotted2sockaddr(const char *host, int port)
+{
+       return str2sockaddr(host, port, AF_UNSPEC, AI_NUMERICHOST | DIE_ON_ERROR);
+}
+
+#undef xsocket_type
+int 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 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 create_and_bind_stream_or_die(const char *bindaddr, int port)
+{
+       return create_and_bind_or_die(bindaddr, port, SOCK_STREAM);
+}
+
+int create_and_bind_dgram_or_die(const char *bindaddr, int port)
+{
+       return create_and_bind_or_die(bindaddr, port, SOCK_DGRAM);
+}
+
+
+int 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 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* 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* xmalloc_sockaddr2host(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, 0);
+}
+
+char* xmalloc_sockaddr2host_noport(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, IGNORE_PORT);
+}
+
+char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, NI_NAMEREQD | IGNORE_PORT);
+}
+char* xmalloc_sockaddr2dotted(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, NI_NUMERICHOST);
+}
+
+char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa)
+{
+       return sockaddr2str(sa, NI_NUMERICHOST | IGNORE_PORT);
+}
diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c
new file mode 100644 (file)
index 0000000..84b9f92
--- /dev/null
@@ -0,0 +1,763 @@
+/* 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 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 *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 *xmalloc(size_t size)
+{
+       void *ptr = malloc(size);
+       if (ptr == NULL && size != 0)
+               bb_error_msg_and_die(bb_msg_memory_exhausted);
+       return ptr;
+}
+
+// Die if we can't resize previously allocated memory.  (This returns a pointer
+// to the new memory, which may or may not be the same as the old memory.
+// It'll copy the contents to a new chunk and free the old one if necessary.)
+void *xrealloc(void *ptr, size_t size)
+{
+       ptr = realloc(ptr, size);
+       if (ptr == NULL && size != 0)
+               bb_error_msg_and_die(bb_msg_memory_exhausted);
+       return ptr;
+}
+#endif /* DMALLOC */
+
+// Die if we can't allocate and zero size bytes of memory.
+void *xzalloc(size_t size)
+{
+       void *ptr = xmalloc(size);
+       memset(ptr, 0, size);
+       return ptr;
+}
+
+// Die if we can't copy a string to freshly allocated memory.
+char * xstrdup(const char *s)
+{
+       char *t;
+
+       if (s == NULL)
+               return NULL;
+
+       t = strdup(s);
+
+       if (t == NULL)
+               bb_error_msg_and_die(bb_msg_memory_exhausted);
+
+       return t;
+}
+
+// Die if we can't allocate n+1 bytes (space for the null terminator) and copy
+// the (possibly truncated to length n) string into it.
+char *xstrndup(const char *s, int n)
+{
+       int m;
+       char *t;
+
+       if (ENABLE_DEBUG && s == NULL)
+               bb_error_msg_and_die("xstrndup bug");
+
+       /* We can just xmalloc(n+1) and strncpy into it, */
+       /* but think about xstrndup("abc", 10000) wastage! */
+       m = n;
+       t = (char*) s;
+       while (m) {
+               if (!*t) break;
+               m--;
+               t++;
+       }
+       n -= m;
+       t = xmalloc(n + 1);
+       t[n] = '\0';
+
+       return memcpy(t, s, n);
+}
+
+// Die if we can't open a file and return a FILE * to it.
+// Notice we haven't got xfread(), This is for use with fscanf() and friends.
+FILE *xfopen(const char *path, const char *mode)
+{
+       FILE *fp = fopen(path, mode);
+       if (fp == NULL)
+               bb_perror_msg_and_die("can't open '%s'", path);
+       return fp;
+}
+
+// Die if we can't open a file and return a fd.
+int xopen3(const char *pathname, int flags, int mode)
+{
+       int ret;
+
+       ret = open(pathname, flags, mode);
+       if (ret < 0) {
+               bb_perror_msg_and_die("can't open '%s'", pathname);
+       }
+       return ret;
+}
+
+// Die if we can't open an existing file and return a fd.
+int xopen(const char *pathname, int flags)
+{
+       return xopen3(pathname, flags, 0666);
+}
+
+// Warn if we can't open a file and return a fd.
+int 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 open_or_warn(const char *pathname, int flags)
+{
+       return open3_or_warn(pathname, flags, 0666);
+}
+
+void xunlink(const char *pathname)
+{
+       if (unlink(pathname))
+               bb_perror_msg_and_die("can't remove file '%s'", pathname);
+}
+
+void 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 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 xpipe(int filedes[2])
+{
+       if (pipe(filedes))
+               bb_perror_msg_and_die("can't create pipe");
+}
+
+// Turn on nonblocking I/O on a fd
+int ndelay_on(int fd)
+{
+       return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);
+}
+
+int close_on_exec_on(int fd)
+{
+       return fcntl(fd, F_SETFD, FD_CLOEXEC);
+}
+
+int ndelay_off(int fd)
+{
+       return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) & ~O_NONBLOCK);
+}
+
+void xdup2(int from, int to)
+{
+       if (dup2(from, to) != to)
+               bb_perror_msg_and_die("can't duplicate file descriptor");
+}
+
+// "Renumber" opened fd
+void 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 xwrite(int fd, const void *buf, size_t count)
+{
+       if (count) {
+               ssize_t size = full_write(fd, buf, count);
+               if (size != count)
+                       bb_error_msg_and_die("short write");
+       }
+}
+
+// Die with an error message if we can't lseek to the right spot.
+off_t xlseek(int fd, off_t offset, int whence)
+{
+       off_t off = lseek(fd, offset, whence);
+       if (off == (off_t)-1) {
+               if (whence == SEEK_SET)
+                       bb_perror_msg_and_die("lseek(%"OFF_FMT"u)", offset);
+               bb_perror_msg_and_die("lseek");
+       }
+       return off;
+}
+
+// Die with supplied filename if this FILE * has ferror set.
+void die_if_ferror(FILE *fp, const char *fn)
+{
+       if (ferror(fp)) {
+               /* 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 die_if_ferror_stdout(void)
+{
+       die_if_ferror(stdout, bb_msg_standard_output);
+}
+
+// Die with an error message if we have trouble flushing stdout.
+void xfflush_stdout(void)
+{
+       if (fflush(stdout)) {
+               bb_perror_msg_and_die(bb_msg_standard_output);
+       }
+}
+
+void xsetenv(const char *key, const char *value)
+{
+       if (setenv(key, value, 1))
+               bb_error_msg_and_die(bb_msg_memory_exhausted);
+}
+
+/* Converts unsigned long long value into compact 4-char
+ * representation. Examples: "1234", "1.2k", " 27M", "123T"
+ * String is not terminated (buf[4] is untouched) */
+void 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..." */
+       }
+}
+
+/* Converts unsigned long long value into compact 5-char representation.
+ * String is not terminated (buf[5] is untouched) */
+void 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 *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 *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.
+// Int should always be 32 bits on any remotely Unix-like system, see
+// http://www.unix.org/whitepapers/64bit.html for the reasons why.
+
+static char local_buf[12];
+
+// Convert unsigned integer to ascii using a static buffer (returned).
+char *utoa(unsigned n)
+{
+       *(utoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0';
+
+       return local_buf;
+}
+
+// Convert signed integer to ascii using a static buffer (returned).
+char *itoa(int n)
+{
+       *(itoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0';
+
+       return local_buf;
+}
+
+// Emit a string of hex representation of bytes
+char *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;
+}
+
+// Die with an error message if we can't set gid.  (Because resource limits may
+// limit this user to a given number of processes, and if that fills up the
+// setgid() will fail and we'll _still_be_root_, which is bad.)
+void xsetgid(gid_t gid)
+{
+       if (setgid(gid)) bb_perror_msg_and_die("setgid");
+}
+
+// Die with an error message if we can't set uid.  (See xsetgid() for why.)
+void xsetuid(uid_t uid)
+{
+       if (setuid(uid)) bb_perror_msg_and_die("setuid");
+}
+
+// Return how long the file at fd is, if there's any way to determine it.
+#ifdef UNUSED
+off_t fdlength(int fd)
+{
+       off_t bottom = 0, top = 0, pos;
+       long size;
+
+       // If the ioctl works for this, return it.
+
+       if (ioctl(fd, BLKGETSIZE, &size) >= 0) return size*512;
+
+       // FIXME: explain why lseek(SEEK_END) is not used here!
+
+       // If not, do a binary search for the last location we can read.  (Some
+       // block devices don't do BLKGETSIZE right.)
+
+       do {
+               char temp;
+
+               pos = bottom + (top - bottom) / 2;
+
+               // If we can read from the current location, it's bigger.
+
+               if (lseek(fd, pos, SEEK_SET)>=0 && safe_read(fd, &temp, 1)==1) {
+                       if (bottom == top) bottom = top = (top+1) * 2;
+                       else bottom = pos;
+
+               // If we can't, it's smaller.
+
+               } else {
+                       if (bottom == top) {
+                               if (!top) return 0;
+                               bottom = top/2;
+                       }
+                       else top = pos;
+               }
+       } while (bottom + 1 != top);
+
+       return pos + 1;
+}
+#endif
+
+int 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 malloc() enough space and do an
+// sprintf() into that space.
+char *xasprintf(const char *format, ...)
+{
+       va_list p;
+       int r;
+       char *string_ptr;
+
+#if 1
+       // GNU extension
+       va_start(p, format);
+       r = vasprintf(&string_ptr, format, p);
+       va_end(p);
+#else
+       // Bloat for systems that haven't got the GNU extension.
+       va_start(p, format);
+       r = vsnprintf(NULL, 0, format, p);
+       va_end(p);
+       string_ptr = xmalloc(r+1);
+       va_start(p, format);
+       r = vsnprintf(string_ptr, r+1, format, p);
+       va_end(p);
+#endif
+
+       if (r < 0)
+               bb_error_msg_and_die(bb_msg_memory_exhausted);
+       return string_ptr;
+}
+
+#if 0 /* If we will ever meet a libc which hasn't [f]dprintf... */
+int 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
+
+// Die with an error message if we can't copy an entire FILE * to stdout, then
+// close that file.
+void xprint_and_close_file(FILE *file)
+{
+       fflush(stdout);
+       // copyfd outputs error messages for us.
+       if (bb_copyfd_eof(fileno(file), 1) == -1)
+               xfunc_die();
+
+       fclose(file);
+}
+
+// Die if we can't chdir to a new path.
+void xchdir(const char *path)
+{
+       if (chdir(path))
+               bb_perror_msg_and_die("chdir(%s)", path);
+}
+
+void 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 *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 *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 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 xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen)
+{
+       if (bind(sockfd, my_addr, addrlen)) bb_perror_msg_and_die("bind");
+}
+
+// Die with an error message if we can't listen for connections on a socket.
+void xlisten(int s, int backlog)
+{
+       if (listen(s, backlog)) bb_perror_msg_and_die("listen");
+}
+
+/* Die with an error message if sendto failed.
+ * Return bytes sent otherwise  */
+ssize_t 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 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 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
+}
+
+/* It is perfectly ok to pass in a NULL for either width or for
+ * height, in which case that value will not be set.  */
+int get_terminal_width_height(int fd, int *width, int *height)
+{
+       struct winsize win = { 0, 0, 0, 0 };
+       int ret = ioctl(fd, TIOCGWINSZ, &win);
+
+       if (height) {
+               if (!win.ws_row) {
+                       char *s = getenv("LINES");
+                       if (s) win.ws_row = atoi(s);
+               }
+               if (win.ws_row <= 1 || win.ws_row >= 30000)
+                       win.ws_row = 24;
+               *height = (int) win.ws_row;
+       }
+
+       if (width) {
+               if (!win.ws_col) {
+                       char *s = getenv("COLUMNS");
+                       if (s) win.ws_col = atoi(s);
+               }
+               if (win.ws_col <= 1 || win.ws_col >= 30000)
+                       win.ws_col = 80;
+               *width = (int) win.ws_col;
+       }
+
+       return ret;
+}
+
+void ioctl_or_perror_and_die(int fd, unsigned request, void *argp, const char *fmt,...)
+{
+       va_list p;
+
+       if (ioctl(fd, request, argp) < 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();
+       }
+}
+
+int 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 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;
+}
+void bb_xioctl(int fd, unsigned request, void *argp, const char *ioctl_name)
+{
+       if (ioctl(fd, request, argp) < 0)
+               bb_simple_perror_msg_and_die(ioctl_name);
+}
+#else
+int 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;
+}
+void bb_xioctl(int fd, unsigned request, void *argp)
+{
+       if (ioctl(fd, request, argp) < 0)
+               bb_perror_msg_and_die("ioctl %#x failed", request);
+}
+#endif
diff --git a/libbb/xgetcwd.c b/libbb/xgetcwd.c
new file mode 100644 (file)
index 0000000..c194e23
--- /dev/null
@@ -0,0 +1,41 @@
+/* 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>
+*/
+
+#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 *
+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..3bb522d
--- /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 *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..706a3d9
--- /dev/null
@@ -0,0 +1,112 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *  xreadlink.c - safe implementation of readlink.
+ *  Returns a NULL on failure...
+ */
+
+#include "libbb.h"
+
+/*
+ * NOTE: This function returns a malloced char* that you will have to free
+ * yourself.
+ */
+char *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 *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 *xmalloc_readlink_or_warn(const char *path)
+{
+       char *buf = xmalloc_readlink(path);
+       if (!buf) {
+               /* EINVAL => "file: Invalid argument" => puzzled user */
+               bb_error_msg("%s: cannot read link (not a symlink?)", path);
+       }
+       return buf;
+}
+
+/* UNUSED */
+#if 0
+char *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/xregcomp.c b/libbb/xregcomp.c
new file mode 100644 (file)
index 0000000..157132c
--- /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* 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 xregcomp(regex_t *preg, const char *regex, int cflags)
+{
+       char *errmsg = regcomp_or_errmsg(preg, regex, cflags);
+       if (errmsg) {
+               bb_error_msg_and_die("xregcomp: %s", 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..3fe70f4
--- /dev/null
@@ -0,0 +1,1059 @@
+/* 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
+
+/* 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;
+}
+
+/**********************************************************************/
+
+/* 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(_PATH_PASSWD, "r");
+               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(_PATH_GROUP, "r");
+               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(_PATH_SHADOW, "r");
+               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
+
+int initgroups(const char *user, gid_t gid)
+{
+       FILE *grfile;
+       gid_t *group_list;
+       int num_groups, rv;
+       char **m;
+       struct group group;
+       char buff[PWD_BUFFER_SIZE];
+
+       rv = -1;
+       grfile = fopen(_PATH_GROUP, "r");
+       if (grfile != NULL) {
+
+               /* We alloc space for 8 gids at a time. */
+               group_list = xmalloc(8 * sizeof(gid_t *));
+               *group_list = gid;
+               num_groups = 1;
+
+               while (!bb__pgsreader(bb__parsegrent, &group, buff, sizeof(buff), grfile)) {
+                       assert(group.gr_mem); /* Must have at least a NULL terminator. */
+                       if (group.gr_gid != gid) {
+                               for (m = group.gr_mem; *m; m++) {
+                                       if (!strcmp(*m, user)) {
+                                               if (!(num_groups & 7)) {
+                                                       gid_t *tmp = xrealloc(group_list,
+                                                                       (num_groups+8) * sizeof(gid_t *));
+                                                       group_list = tmp;
+                                               }
+                                               group_list[num_groups++] = group.gr_gid;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               rv = setgroups(num_groups, group_list);
+               free(group_list);
+               fclose(grfile);
+       }
+
+       return rv;
+}
+
+int putpwent(const struct passwd *__restrict p, FILE *__restrict f)
+{
+       int rv = -1;
+
+       if (!p || !f) {
+               errno = EINVAL;
+       } else {
+               /* No extra thread locking is needed above what fprintf does. */
+               if (fprintf(f, "%s:%s:%lu:%lu:%s:%s:%s\n",
+                                       p->pw_name, p->pw_passwd,
+                                       (unsigned long)(p->pw_uid),
+                                       (unsigned long)(p->pw_gid),
+                                       p->pw_gecos, p->pw_dir, p->pw_shell) >= 0
+                       ) {
+                       rv = 0;
+               }
+       }
+
+       return rv;
+}
+
+int putgrent(const struct group *__restrict p, FILE *__restrict f)
+{
+       static const char format[] 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..d55edc3
--- /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(GETXXKEY_R_PATHNAME, "r");
+       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..b0085c4
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+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 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;
+}
+
+/* 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 is actually set to -1)
+ */
+void parse_chown_usergroup_or_die(struct bb_uidgid_t *u, char *user_group)
+{
+       char *group;
+
+       u->uid = -1;
+       u->gid = -1;
+
+       /* 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';
+               if (!get_uidgid(u, user_group, 1))
+                       bb_error_msg_and_die("unknown user/group %s", 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..c57d997
--- /dev/null
@@ -0,0 +1,258 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Login/Password Management Utilities"
+
+config FEATURE_SHADOWPASSWDS
+       bool "Support for shadow passwords"
+       default n
+       help
+         Build support for shadow password in /etc/shadow.  This file is only
+         readable by root and thus the encrypted passwords are no longer
+         publicly readable.
+
+config USE_BB_SHADOW
+       bool "Use busybox shadow password functions"
+       default y
+       depends on USE_BB_PWD_GRP && FEATURE_SHADOWPASSWDS
+       help
+           If you leave this disabled, busybox will use the system's shadow
+           password handling functions.  And if you are using the GNU C library
+           (glibc), you will then need to install the /etc/nsswitch.conf
+           configuration file and the required /lib/libnss_* libraries in
+           order for the shadow password functions to work.  This generally
+           makes your embedded system quite a bit larger.
+
+           Enabling this option will cause busybox to directly access the
+           system's /etc/shadow file when handling shadow passwords.  This
+           makes your system smaller and I will get fewer emails asking about
+           how glibc NSS works).  When this option is enabled, you will not be
+           able to use PAM to access shadow passwords from remote LDAP
+           password servers and whatnot.
+
+config USE_BB_PWD_GRP
+       bool "Use internal password and group functions rather than system functions"
+       default n
+       help
+           If you leave this disabled, busybox will use the system's password
+           and group functions.  And if you are using the GNU C library
+           (glibc), you will then need to install the /etc/nsswitch.conf
+           configuration file and the required /lib/libnss_* libraries in
+           order for the password and group functions to work.  This generally
+           makes your embedded system quite a bit larger.
+
+           Enabling this option will cause busybox to directly access the
+           system's /etc/password, /etc/group files (and your system will be
+           smaller, and I will get fewer emails asking about how glibc NSS
+           works).  When this option is enabled, you will not be able to use
+           PAM to access remote LDAP password servers and whatnot.  And if you
+           want hostname resolution to work with glibc, you still need the
+           /lib/libnss_* libraries.
+
+           If you enable this option, it will add about 1.5k to busybox.
+
+config ADDGROUP
+       bool "addgroup"
+       default n
+       help
+         Utility for creating a new group account.
+
+config 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
+         Applet for crypting a string.
+
+config CHPASSWD
+       bool "chpasswd"
+       default n
+       help
+         chpasswd  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..367c6b9
--- /dev/null
@@ -0,0 +1,183 @@
+/* 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)
+{
+       FILE *file;
+       struct group gr;
+
+       /* 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 */
+       file = xfopen(bb_path_group_file, "a");
+       /* group:passwd:gid:userlist */
+       fprintf(file, "%s:x:%u:\n", group, (unsigned)gr.gr_gid);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               fclose(file);
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       file = fopen_or_warn(bb_path_gshadow_file, "a");
+       if (file) {
+               fprintf(file, "%s:!::\n", group);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       fclose(file);
+       }
+#endif
+}
+
+#if ENABLE_FEATURE_ADDUSER_TO_GROUP
+static void add_user_to_group(char **args,
+               const char *path,
+               FILE *(*fopen_func)(const char *fileName, const char *mode))
+{
+       char *line;
+       int len = strlen(args[1]);
+       llist_t *plist = NULL;
+       FILE *group_file;
+
+       group_file = fopen_func(path, "r");
+
+       if (!group_file) return;
+
+       while ((line = xmalloc_getline(group_file))) {
+               /* Find the group */
+               if (!strncmp(line, args[1], len)
+                && line[len] == ':'
+               ) {
+                       /* Add the new user */
+                       line = xasprintf("%s%s%s", line,
+                                               last_char_is(line, ':') ? "" : ",",
+                                               args[0]);
+               }
+               llist_add_to_end(&plist, line);
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               fclose(group_file);
+               group_file = fopen_func(path, "w");
+               while ((line = llist_pop(&plist))) {
+                       if (group_file)
+                               fprintf(group_file, "%s\n", line);
+                       free(line);
+               }
+               if (group_file)
+                       fclose(group_file);
+       } else {
+               group_file = fopen_func(path, "w");
+               if (group_file)
+                       while ((line = llist_pop(&plist)))
+                               fprintf(group_file, "%s\n", line);
+       }
+}
+#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 ATTRIBUTE_UNUSED, 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 */
+               xgroup2gid(argv[1]); /* unknown group: exit */
+               /* check if user is already in this group */
+               gr = getgrnam(argv[1]);
+               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;
+                       }
+               }
+               add_user_to_group(argv, bb_path_group_file, xfopen);
+#if ENABLE_FEATURE_SHADOWPASSWDS
+               add_user_to_group(argv, bb_path_gshadow_file, fopen_or_warn);
+#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..cd68015
--- /dev/null
@@ -0,0 +1,178 @@
+/* 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 && getgrgid(p->pw_uid)))
+               p->pw_uid++;
+
+       if (!p->pw_gid) {
+               /* new gid = uid */
+               p->pw_gid = p->pw_uid;
+               if (getgrnam(p->pw_name))
+                       bb_error_msg_and_die("group name '%s' is in use", p->pw_name);
+       }
+
+       if (p->pw_uid > max)
+               bb_error_msg_and_die("no free uids left");
+}
+
+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) ATTRIBUTE_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 ATTRIBUTE_UNUSED, char **argv)
+{
+       struct passwd pw;
+       const char *usegroup = NULL;
+       FILE *file;
+
+#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) : 0; /* exits on failure */
+
+       /* make sure everything is kosher and setup uid && maybe gid */
+       passwd_study(&pw);
+
+       /* add to passwd */
+       file = xfopen(bb_path_passwd_file, "a");
+       //fseek(file, 0, SEEK_END); /* paranoia, "a" should ensure that anyway */
+       if (putpwent(&pw, file) != 0) {
+               bb_perror_nomsg_and_die();
+       }
+       /* do fclose even if !ENABLE_FEATURE_CLEAN_UP.
+        * We will exec passwd, files must be flushed & closed before that! */
+       fclose(file);
+
+#if ENABLE_FEATURE_SHADOWPASSWDS
+       /* add to shadow if necessary */
+       file = fopen_or_warn(bb_path_shadow_file, "a");
+       if (file) {
+               //fseek(file, 0, SEEK_END);
+               fprintf(file, "%s:!:%u:0:99999:7:::\n",
+                               pw.pw_name,             /* username */
+                               (unsigned)(time(NULL) / 86400) /* sp->sp_lstchg */
+                               /*0,*/                  /* sp->sp_min */
+                               /*99999,*/              /* sp->sp_max */
+                               /*7*/                   /* sp->sp_warn */
+               );
+               fclose(file);
+       }
+#endif
+
+       /* add to group */
+       /* addgroup should be responsible for dealing w/ gshadow */
+       /* if using a pre-existing group, don't create one */
+       if (!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..83e5e0c
--- /dev/null
@@ -0,0 +1,73 @@
+/* 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
+#include <getopt.h>
+
+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 ATTRIBUTE_UNUSED, 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_getline(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);
+               }
+
+               /* 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);
+               if (rc == 0) /* no lines updated, no errors detected */
+#endif
+                       rc = update_passwd(bb_path_passwd_file, name, pass);
+               /* 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);
+       }
+
+       return 0;
+}
diff --git a/loginutils/cryptpw.c b/loginutils/cryptpw.c
new file mode 100644 (file)
index 0000000..c5170c6
--- /dev/null
@@ -0,0 +1,28 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * cryptpw.c
+ *
+ * Cooked from passwd.c by Thomas Lundquist <thomasez@zelow.no>
+ */
+
+#include "libbb.h"
+
+int cryptpw_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int cryptpw_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       char salt[sizeof("$N$XXXXXXXX")];
+
+       if (!getopt32(argv, "a:", NULL) || argv[optind - 1][0] != 'd') {
+               strcpy(salt, "$1$");
+               /* Too ugly, and needs even more magic to handle endianness: */
+               //((uint32_t*)&salt)[0] = '$' + '1'*0x100 + '$'*0x10000;
+               /* Hope one day gcc will do it itself (inlining strcpy) */
+               crypt_make_salt(salt + 3, 4, 0); /* md5 */
+       } else {
+               crypt_make_salt(salt, 1, 0);     /* des */
+       }
+
+       puts(pw_encrypt(argv[optind] ? argv[optind] : xmalloc_getline(stdin), salt));
+
+       return 0;
+}
diff --git a/loginutils/deluser.c b/loginutils/deluser.c
new file mode 100644 (file)
index 0000000..c67ad72
--- /dev/null
@@ -0,0 +1,125 @@
+/* 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"
+
+/* Status */
+#define STATUS_OK            0
+#define NAME_NOT_FOUND       1
+#define MEMBER_NOT_FOUND     2
+
+static void del_line_matching(char **args,
+               const char *filename,
+               FILE *(*fopen_func)(const char *fileName, const char *mode))
+{
+       FILE *passwd;
+       smallint error = NAME_NOT_FOUND;
+       char *name = (ENABLE_FEATURE_DEL_USER_FROM_GROUP && args[2]) ? args[2] : args[1];
+       char *line, *del;
+       char *new = xzalloc(1);
+
+       passwd = fopen_func(filename, "r");
+       if (passwd) {
+               while ((line = xmalloc_fgets(passwd))) {
+                       int len = strlen(name);
+
+                       if (strncmp(line, name, len) == 0
+                        && line[len] == ':'
+                       ) {
+                               error = STATUS_OK;
+                               if (ENABLE_FEATURE_DEL_USER_FROM_GROUP) {
+                                       struct group *gr;
+                                       char *p;
+                                       if (args[2]
+                                        /* There were two args on commandline */
+                                        && (gr = getgrnam(name))
+                                        /* The group was not deleted in the meanwhile */
+                                        && (p = strrchr(line, ':'))
+                                        /* We can find a pointer to the last ':' */
+                                       ) {
+                                               error = MEMBER_NOT_FOUND;
+                                               /* Move past ':' (worst case to '\0') and cut the line */
+                                               p[1] = '\0';
+                                               /* Reuse p */
+                                               for (p = xzalloc(1); *gr->gr_mem != NULL; gr->gr_mem++) {
+                                                       /* Add all the other group members */
+                                                       if (strcmp(args[1], *gr->gr_mem) != 0) {
+                                                               del = p;
+                                                               p = xasprintf("%s%s%s", p, p[0] ? "," : "", *gr->gr_mem);
+                                                               free(del);
+                                                       } else
+                                                               error = STATUS_OK;
+                                               }
+                                               /* Recompose the line */
+                                               line = xasprintf("%s%s\n", line, p);
+                                               if (ENABLE_FEATURE_CLEAN_UP) free(p);
+                                       } else
+                                               goto skip;
+                               }
+                       }
+                       del = new;
+                       new = xasprintf("%s%s", new, line);
+                       free(del);
+ skip:
+                       free(line);
+               }
+
+               if (ENABLE_FEATURE_CLEAN_UP) fclose(passwd);
+
+               if (error) {
+                       if (ENABLE_FEATURE_DEL_USER_FROM_GROUP && error == MEMBER_NOT_FOUND) {
+                               /* Set the correct values for error message */
+                               filename = name;
+                               name = args[1];
+                       }
+                       bb_error_msg("can't find %s in %s", name, filename);
+               } else {
+                       passwd = fopen_func(filename, "w");
+                       if (passwd) {
+                               fputs(new, passwd);
+                               if (ENABLE_FEATURE_CLEAN_UP) fclose(passwd);
+                       }
+               }
+       }
+       free(new);
+}
+
+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))
+       ) {
+               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')
+                       ) {
+                               del_line_matching(argv, bb_path_passwd_file, xfopen);
+                               if (ENABLE_FEATURE_SHADOWPASSWDS)
+                                       del_line_matching(argv, bb_path_shadow_file, fopen_or_warn);
+                       } else if (ENABLE_DESKTOP && ENABLE_DELGROUP && getpwnam(argv[1]))
+                               bb_error_msg_and_die("can't remove primary group of user %s", argv[1]);
+               }
+               del_line_matching(argv, bb_path_group_file, xfopen);
+               if (ENABLE_FEATURE_SHADOWPASSWDS)
+                       del_line_matching(argv, bb_path_gshadow_file, fopen_or_warn);
+               return EXIT_SUCCESS;
+       } else
+               bb_show_usage();
+}
diff --git a/loginutils/getty.c b/loginutils/getty.c
new file mode 100644 (file)
index 0000000..da0dce3
--- /dev/null
@@ -0,0 +1,778 @@
+/* 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>
+#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>
+#include <time.h>
+#if ENABLE_FEATURE_WTMP
+extern void updwtmp(const char *filename, const struct utmp *ut);
+#endif
+#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);
+               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)
+{
+       /*
+        * Initial termios settings: 8-bit characters, raw-mode, blocking i/o.
+        * Special characters are set after we have read the login name; all
+        * reads will be done in raw mode anyway. Errors will be dealt with
+        * later on.
+        */
+#ifdef __linux__
+       /* flush input and output queues, important for modems! */
+       ioctl(0, TCFLSH, TCIOFLUSH);
+#endif
+
+       tp->c_cflag = CS8 | HUPCL | CREAD | speed;
+       if (op->flags & F_LOCAL)
+               tp->c_cflag |= CLOCAL;
+
+       tp->c_iflag = tp->c_lflag = tp->c_line = 0;
+       tp->c_oflag = OPOST | ONLCR;
+       tp->c_cc[VMIN] = 1;
+       tp->c_cc[VTIME] = 0;
+
+       /* Optionally enable hardware flow control */
+#ifdef CRTSCTS
+       if (op->flags & F_RTSCTS)
+               tp->c_cflag |= CRTSCTS;
+#endif
+
+       ioctl(0, TCSETS, tp);
+
+       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 */
+       ioctl(0, TCSETS, tp);
+
+       /*
+        * Wait for a while, then read everything the modem has said so far and
+        * try to extract the speed of the dial-in call.
+        */
+       sleep(1);
+       nread = safe_read(0, 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;
+       ioctl(0, TCSETS, 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);
+
+       /* 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(0, &c, 1) < 1) {
+                               if (errno == EINTR || errno == EIO)
+                                       exit(0);
+                               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(1, 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(1, erase[cp->parity], 3);
+                                       bp--;
+                               }
+                               break;
+                       case CTL('D'):
+                               exit(0);
+                       default:
+                               if (!isascii(ascval) || !isprint(ascval)) {
+                                       /* ignore garbage characters */
+                               } else if (bp - logname >= size_logname - 1) {
+                                       bb_error_msg_and_die("%s: input overrun", op->tty);
+                               } else {
+                                       full_write(1, &c, 1); /* echo the character */
+                                       *bp++ = ascval; /* and store it */
+                               }
+                               break;
+                       }
+               }
+       }
+       /* Handle names with upper case and no lower case. */
+
+#ifdef HANDLE_ALLCAPS
+       cp->capslock = 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 */
+       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_time = 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 ATTRIBUTE_UNUSED, 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(DEBUGTERM, "w");
+       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.
+        */
+       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");
+               full_write(1, 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(0, &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;
+                       termios.c_cflag &= ~CBAUD;
+                       termios.c_cflag |= options.speeds[baud_index];
+                       ioctl(0, TCSETS, &termios);
+               }
+       }
+
+       /* Disable timer. */
+       alarm(0);
+
+       /* Finalize the termios settings. */
+       termios_final(&options, &termios, &chardata);
+
+       /* Now the newline character should be properly written. */
+       full_write(1, "\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..e8fe74e
--- /dev/null
@@ -0,0 +1,502 @@
+/* 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 picky)
+{
+       struct utmp *ut;
+       pid_t pid = getpid();
+
+       setutent();
+
+       /* First, try to find a valid utmp entry for this process.  */
+       while ((ut = getutent()))
+               if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] &&
+               (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS))
+                       break;
+
+       /* If there is one, just use it, otherwise create a new one.  */
+       if (ut) {
+               *utptr = *ut;
+       } else {
+               if (picky)
+                       bb_error_msg_and_die("no utmp entry found");
+
+               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_time = time(NULL);
+       }
+       if (!picky)     /* root login */
+               memset(utptr->ut_host, 0, sizeof(utptr->ut_host));
+}
+
+/*
+ * write_utent - put a USER_PROCESS entry in the utmp file
+ *
+ *     write_utent changes the type of the current utmp entry to
+ *     USER_PROCESS.  the wtmp file will be updated as well.
+ */
+static void write_utent(struct utmp *utptr, const char *username)
+{
+       utptr->ut_type = USER_PROCESS;
+       strncpy(utptr->ut_user, username, sizeof(utptr->ut_user));
+       utptr->ut_time = 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, picky) ((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;
+
+       if (access("/etc/nologin", F_OK))
+               return;
+
+       fp = fopen("/etc/nologin", "r");
+       if (fp) {
+               while ((c = getc(fp)) != EOF)
+                       bb_putchar((c=='\n') ? '\r' : c);
+               fflush(stdout);
+               fclose(fp);
+       } else
+               puts("\r\nSystem closed for routine maintenance\r");
+       exit(1);
+}
+#else
+static ALWAYS_INLINE void die_if_nologin(void) {}
+#endif
+
+#if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM
+static int check_securetty(void)
+{
+       FILE *fp;
+       int i;
+       char buf[256];
+
+       fp = fopen("/etc/securetty", "r");
+       if (!fp) {
+               /* A missing securetty file is not an error. */
+               return 1;
+       }
+       while (fgets(buf, sizeof(buf)-1, fp)) {
+               for (i = strlen(buf)-1; i >= 0; --i) {
+                       if (!isspace(buf[i]))
+                               break;
+               }
+               buf[++i] = '\0';
+               if (!buf[0] || (buf[0] == '#'))
+                       continue;
+               if (strcmp(buf, short_tty) == 0) {
+                       fclose(fp);
+                       return 1;
+               }
+       }
+       fclose(fp);
+       return 0;
+}
+#else
+static ALWAYS_INLINE int check_securetty(void) { return 1; }
+#endif
+
+static void get_username_or_die(char *buf, int size_buf)
+{
+       int c, cntdown;
+
+       cntdown = EMPTY_USERNAME_COUNT;
+ prompt:
+       print_login_prompt();
+       /* skip whitespace */
+       do {
+               c = getchar();
+               if (c == EOF) exit(1);
+               if (c == '\n') {
+                       if (!--cntdown) exit(1);
+                       goto prompt;
+               }
+       } while (isspace(c));
+
+       *buf++ = c;
+       if (!fgets(buf, size_buf-2, stdin))
+               exit(1);
+       if (!strchr(buf, '\n'))
+               exit(1);
+       while (isgraph(*buf)) buf++;
+       *buf = '\0';
+}
+
+static void motd(void)
+{
+       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 ATTRIBUTE_UNUSED)
+{
+       /* This is the escape hatch!  Poor serial line users and the like
+        * arrive here when their connection is broken.
+        * We don't want to block here */
+       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 ATTRIBUTE_UNUSED, 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 amroot;
+       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[TTYNAME_SIZE];
+       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
+
+       short_tty = full_tty;
+       username[0] = '\0';
+       signal(SIGALRM, alarm_handler);
+       alarm(TIMEOUT);
+
+       /* More of suid paranoia if called by non-root */
+       amroot = !sanitize_env_if_suid(); /* Clear dangerous stuff, set PATH */
+
+       /* 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 (!amroot)
+                       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(0) || !isatty(1) || !isatty(2))
+               return EXIT_FAILURE;            /* Must be a terminal */
+       safe_strncpy(full_tty, "UNKNOWN", sizeof(full_tty));
+       tmp = ttyname(0);
+       if (tmp) {
+               safe_strncpy(full_tty, tmp, sizeof(full_tty));
+               if (strncmp(full_tty, "/dev/", 5) == 0)
+                       short_tty = full_tty + 5;
+       }
+
+       read_or_build_utent(&utent, !amroot);
+
+       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_NOWAIT, 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';
+       }
+
+       alarm(0);
+       if (!amroot)
+               die_if_nologin();
+
+       write_utent(&utent, username);
+
+#if ENABLE_SELINUX
+       if (is_selinux_enabled()) {
+               security_context_t old_tty_sid, new_tty_sid;
+
+               if (get_default_context(username, NULL, &user_sid)) {
+                       bb_error_msg_and_die("cannot get SID for %s",
+                                       username);
+               }
+               if (getfilecon(full_tty, &old_tty_sid) < 0) {
+                       bb_perror_msg_and_die("getfilecon(%s) failed",
+                                       full_tty);
+               }
+               if (security_compute_relabel(user_sid, old_tty_sid,
+                                       SECCLASS_CHR_FILE, &new_tty_sid) != 0) {
+                       bb_perror_msg_and_die("security_change_sid(%s) failed",
+                                       full_tty);
+               }
+               if (setfilecon(full_tty, new_tty_sid) != 0) {
+                       bb_perror_msg_and_die("chsid(%s, %s) failed",
+                                       full_tty, new_tty_sid);
+               }
+       }
+#endif
+       /* Try these, but don't complain if they fail.
+        * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */
+       fchown(0, pw->pw_uid, pw->pw_gid);
+       fchmod(0, 0600);
+
+       /* We trust environment only if we run by root */
+       if (ENABLE_LOGIN_SCRIPTS && amroot) {
+               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");
+               }
+       }
+
+       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);
+#if ENABLE_SELINUX
+       /* well, a simple setexeccon() here would do the job as well,
+        * but let's play the game for now */
+       set_current_security_context(user_sid);
+#endif
+
+       // util-linux login also does:
+       // /* start new session */
+       // setsid();
+       // /* TIOCSCTTY: steal tty from other process group */
+       // if (ioctl(0, TIOCSCTTY, 1)) error_msg...
+       // 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 */
+       signal(SIGALRM, SIG_DFL);
+       /* Is this correct? This way user can ctrl-c out of /etc/profile,
+        * potentially creating security breach (tested with bash 3.0).
+        * But without this, bash 3.0 will not enable ctrl-c either.
+        * Maybe bash is buggy?
+        * Need to find out what standards say about /bin/login -
+        * should it leave SIGINT etc enabled or disabled? */
+       signal(SIGINT, SIG_DFL);
+
+       /* 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..3353db1
--- /dev/null
@@ -0,0 +1,205 @@
+/* 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 *cipher = NULL;
+       char *cp = NULL;
+       char *ret = NULL; /* failure so far */
+
+       if (myuid && pw->pw_passwd[0]) {
+               orig = bb_askpass(0, "Old password:"); /* returns ptr to static */
+               if (!orig)
+                       goto err_ret;
+               cipher = pw_encrypt(orig, pw->pw_passwd); /* returns ptr to static */
+               if (strcmp(cipher, pw->pw_passwd) != 0) {
+                       syslog(LOG_WARNING, "incorrect password for '%s'",
+                               pw->pw_name);
+                       bb_do_delay(FAIL_DELAY);
+                       puts("Incorrect password");
+                       goto err_ret;
+               }
+       }
+       orig = xstrdup(orig); /* or else bb_askpass() will destroy it */
+       newp = bb_askpass(0, "New password:"); /* returns ptr to static */
+       if (!newp)
+               goto err_ret;
+       newp = xstrdup(newp); /* we are going to bb_askpass() again, so save it */
+       if (ENABLE_FEATURE_PASSWD_WEAK_CHECK
+        && obscure(orig, newp, pw) && myuid)
+               goto err_ret; /* non-root is not allowed to have weak passwd */
+
+       cp = bb_askpass(0, "Retype password:");
+       if (!cp)
+               goto err_ret;
+       if (strcmp(cp, newp)) {
+               puts("Passwords don't match");
+               goto err_ret;
+       }
+
+       crypt_make_salt(salt, 1, 0); /* des */
+       if (algo) { /* MD5 */
+               strcpy(salt, "$1$");
+               crypt_make_salt(salt + 3, 4, 0);
+       }
+       /* pw_encrypt returns ptr to static */
+       ret = xstrdup(pw_encrypt(newp, salt));
+       /* whee, success! */
+
+ err_ret:
+       nuke_str(orig);
+       if (ENABLE_FEATURE_CLEAN_UP) free(orig);
+       nuke_str(newp);
+       if (ENABLE_FEATURE_CLEAN_UP) free(newp);
+       nuke_str(cipher);
+       nuke_str(cp);
+       return ret;
+}
+
+int passwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int passwd_main(int argc ATTRIBUTE_UNUSED, 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, LOG_NOWAIT, 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(bb_getpwuid(NULL, -1, myuid));
+       name = argv[0] ? argv[0] : myname;
+
+       pw = getpwnam(name);
+       if (!pw) bb_error_msg_and_die("unknown user %s", name);
+       if (myuid && pw->pw_uid != myuid) {
+               /* LOGMODE_BOTH */
+               bb_error_msg_and_die("%s can't change password for %s", myname, name);
+       }
+
+#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);
+       if (rc == 0) /* no lines updated, no errors detected */
+#endif
+       {
+               filename = bb_path_passwd_file;
+               rc = update_passwd(bb_path_passwd_file, name, newp);
+       }
+       /* 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..1a35f0e
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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 ATTRIBUTE_UNUSED, 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 = ttyname(2) ? : "none";
+               openlog(applet_name, 0, LOG_AUTH);
+       }
+
+       pw = getpwnam(opt_username);
+       if (!pw)
+               bb_error_msg_and_die("unknown id: %s", opt_username);
+
+       /* Make sure pw->pw_shell is non-NULL.  It may be NULL when NEW_USER
+          is a username that is retrieved via NIS (YP), but that doesn't have
+          a default shell listed.  */
+       if (!pw->pw_shell || !pw->pw_shell[0])
+               pw->pw_shell = (char *)DEFAULT_SHELL;
+
+       if ((cur_uid == 0) || correct_password(pw)) {
+               if (ENABLE_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..17bb15e
--- /dev/null
@@ -0,0 +1,110 @@
+/* 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 ATTRIBUTE_UNUSED junk)
+//{
+//     exit(EXIT_FAILURE);
+//}
+
+
+int sulogin_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sulogin_main(int argc ATTRIBUTE_UNUSED, 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);
+
+       if (argv[optind]) {
+               close(0);
+               close(1);
+               dup(xopen(argv[optind], 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_askpass() 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) {
+               /* cp points to a static buffer that is zeroed every time */
+               cp = bb_askpass(timeout,
+                               "Give root password for system maintenance\n"
+                               "(or type Control-D for normal startup):");
+
+               if (!cp || !*cp) {
+                       bb_info_msg("Normal startup");
+                       return 0;
+               }
+               if (strcmp(pw_encrypt(cp, pwd->pw_passwd), pwd->pw_passwd) == 0) {
+                       break;
+               }
+               bb_do_delay(FAIL_DELAY);
+               bb_error_msg("login incorrect");
+       }
+       memset(cp, 0, strlen(cp));
+//     signal(SIGALRM, SIG_DFL);
+
+       bb_info_msg("System Maintenance Mode");
+
+       USE_SELINUX(renew_current_security_context());
+
+       shell = getenv("SUSHELL");
+       if (!shell)
+               shell = getenv("sushell");
+       if (!shell) {
+               shell = "/bin/sh";
+               if (pwd->pw_shell[0])
+                       shell = pwd->pw_shell;
+       }
+       /* 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..96c1f67
--- /dev/null
@@ -0,0 +1,106 @@
+/* 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 ATTRIBUTE_UNUSED)
+{
+       /* 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 ATTRIBUTE_UNUSED)
+{
+       /* 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       struct vt_mode vtm;
+       struct termios term;
+       struct termios oterm;
+       struct vt_mode ovtm;
+       uid_t uid;
+       struct passwd *pw;
+
+       uid = getuid();
+       pw = getpwuid(uid);
+       if (pw == NULL)
+               bb_error_msg_and_die("unknown uid %d", (int)uid);
+
+       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_FILENO, 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_FILENO, TCSANOW, &oterm);
+       fflush_stdout_and_exit(0);
+}
diff --git a/miscutils/Config.in b/miscutils/Config.in
new file mode 100644 (file)
index 0000000..e149e41
--- /dev/null
@@ -0,0 +1,475 @@
+#
+# 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 DEBUG_CROND_OPTION
+       bool "Support option -d to redirect output to stderr"
+       depends on CROND
+       default n
+       help
+         -d sets loglevel to 0 (most verbose) and directs all output to stderr.
+
+config FEATURE_CROND_CALL_SENDMAIL
+       bool "Using /usr/sbin/sendmail?"
+       default n
+       depends on CROND
+       help
+         Support calling /usr/sbin/sendmail for send cmd outputs.
+
+config CRONTAB
+       bool "crontab"
+       default n
+       select FEATURE_SUID
+       help
+         Crontab manipulates the crontab for a particular user.  Only
+         the superuser may specify a different user and/or crontab directory.
+         Note that Busybox binary must be setuid root for this applet to
+         work properly.
+
+config DC
+       bool "dc"
+       default n
+       help
+         Dc is a reverse-polish desk calculator which supports unlimited
+         precision arithmetic.
+
+config DEVFSD
+       bool "devfsd (obsolete)"
+       default n
+       select FEATURE_SYSLOG
+       help
+         This is deprecated, and will be removed at the end of 2008.
+
+         Provides compatibility with old device names on a devfs systems.
+         You should set it to true if you have devfs enabled.
+         The following keywords in devsfd.conf are supported:
+         "CLEAR_CONFIG", "INCLUDE", "OPTIONAL_INCLUDE", "RESTORE",
+         "PERMISSIONS", "EXECUTE", "COPY", "IGNORE",
+         "MKOLDCOMPAT", "MKNEWCOMPAT","RMOLDCOMPAT", "RMNEWCOMPAT".
+
+          But only if they are written UPPERCASE!!!!!!!!
+
+config DEVFSD_MODLOAD
+       bool "Adds support for MODLOAD keyword in devsfd.conf"
+       default n
+       depends on DEVFSD
+       help
+         This actually doesn't work with busybox modutils but needs
+         the external modutils.
+
+config DEVFSD_FG_NP
+       bool "Enables the -fg and -np options"
+       default n
+       depends on DEVFSD
+       help
+               -fg     Run the daemon in the foreground.
+               -np     Exit  after  parsing  the configuration file. Do not poll for events.
+
+config DEVFSD_VERBOSE
+       bool "Increases logging (and size)"
+       default n
+       depends on DEVFSD
+       help
+         Increases logging to stderr or syslog.
+
+config FEATURE_DEVFS
+       bool "Use devfs names for all devices (obsolete)"
+       default n
+       help
+         This is obsolete and will be going away at the end of 2008..
+
+         This tells busybox to look for names like /dev/loop/0 instead of
+         /dev/loop0.  If your /dev directory has normal names instead of
+         devfs names, you don't want this.
+
+config EJECT
+       bool "eject"
+       default n
+       help
+         Used to eject cdroms.  (defaults to /dev/cdrom)
+
+config 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 LAST
+       bool "last"
+       default n
+       select FEATURE_WTMP
+       help
+         'last' displays a list of the last users that logged into the system.
+
+config LESS
+       bool "less"
+       default n
+       help
+         'less' is a pager, meaning that it displays text files. It possesses
+         a wide array of features, and is an improvement over 'more'.
+
+config FEATURE_LESS_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_FLAGCS
+       bool "Enable flag changes"
+       default n
+       depends on LESS
+       help
+         This enables the ability to change command-line flags within
+         less itself.
+
+config FEATURE_LESS_MARKS
+       bool "Enable marks"
+       default n
+       depends on LESS
+       help
+         Marks enable positions in a file to be stored for easy reference.
+
+config FEATURE_LESS_REGEXP
+       bool "Enable regular expressions"
+       default n
+       depends on LESS
+       help
+         Enable regular expressions, allowing complex file searches.
+
+config HDPARM
+       bool "hdparm"
+       default n
+       help
+         Get/Set hard drive parameters.  Primarily intended for ATA
+         drives.  Adds about 13k (or around 30k if you enable the
+         FEATURE_HDPARM_GET_IDENTITY option)....
+
+config FEATURE_HDPARM_GET_IDENTITY
+       bool "Support obtaining detailed information directly from drives"
+       default y
+       depends on HDPARM
+       help
+         Enables the -I and -i options to obtain detailed information
+         directly from drives about their capabilities and supported ATA
+         feature set. If no device name is specified, hdparm will read
+         identify data from stdin. Enabling this option will add about 16k...
+
+config FEATURE_HDPARM_HDIO_SCAN_HWIF
+       bool "Register an IDE interface (DANGEROUS)"
+       default n
+       depends on HDPARM
+       help
+         Enables the 'hdparm -R' option to register an IDE interface.
+         This is dangerous stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_UNREGISTER_HWIF
+       bool "Un-register an IDE interface (DANGEROUS)"
+       default n
+       depends on HDPARM
+       help
+         Enables the 'hdparm -U' option to un-register an IDE interface.
+         This is dangerous stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_DRIVE_RESET
+       bool "perform device reset (DANGEROUS)"
+       default n
+       depends on HDPARM
+       help
+         Enables the 'hdparm -w' option to perform a device reset.
+         This is dangerous stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_TRISTATE_HWIF
+       bool "tristate device for hotswap (DANGEROUS)"
+       default n
+       depends on HDPARM
+       help
+         Enables the 'hdparm -x' option to tristate device for hotswap,
+         and the '-b' option to get/set bus state.  This is dangerous
+         stuff, so you should probably say N.
+
+config FEATURE_HDPARM_HDIO_GETSET_DMA
+       bool "get/set using_dma flag (DANGEROUS)"
+       default n
+       depends on HDPARM
+       help
+         Enables the 'hdparm -d' option to get/set using_dma flag.
+         This is dangerous stuff, so you should probably say N.
+
+config MAKEDEVS
+       bool "makedevs"
+       default n
+       help
+         'makedevs' is a utility used to create a batch of devices with
+         one command.
+         .
+         There are two choices for command line behaviour, the interface
+         as used by LEAF/Linux Router Project, or a device table file.
+         .
+         'leaf' is traditionally what busybox follows, it allows multiple
+         devices of a particluar type to be created per command.
+         e.g. /dev/hda[0-9]
+         Device properties are passed as command line arguments.
+         .
+         'table' reads device properties from a file or stdin, allowing
+         a batch of unrelated devices to be made with one command.
+         User/group names are allowed as an alternative to uid/gid.
+
+choice
+       prompt "Choose makedevs behaviour"
+       depends on MAKEDEVS
+       default FEATURE_MAKEDEVS_TABLE
+
+config FEATURE_MAKEDEVS_LEAF
+       bool "leaf"
+
+config FEATURE_MAKEDEVS_TABLE
+       bool "table"
+
+endchoice
+
+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 boundprocess is running) it can
+         significantly speed up system startup.
+
+         As readahead(2) blocks until each file has been read, it is best to
+         run this applet as a background job.
+
+config RUNLEVEL
+       bool "runlevel"
+       default n
+       help
+         find the current and previous system runlevel.
+
+         This applet uses utmp but does not rely on busybox supporing
+         utmp on purpose. It is used by e.g. emdebian via /etc/init.d/rc.
+
+config RX
+       bool "rx"
+       default n
+       help
+         Receive files using the Xmodem protocol.
+
+config SCRIPT
+       bool "script"
+       default n
+       help
+         The script makes typescript of terminal session.
+
+config STRINGS
+       bool "strings"
+       default n
+       help
+         strings prints the printable character sequences for each file
+         specified.
+
+config SETSID
+       bool "setsid"
+       default n
+       help
+         setsid runs a program in a new session
+
+config TASKSET
+       bool "taskset"
+       default n
+       help
+         Retrieve or set a processes's CPU affinity.
+         This requires sched_{g,s}etaffinity support in your libc.
+
+config FEATURE_TASKSET_FANCY
+       bool "Fancy output"
+       default y
+       depends on TASKSET
+       help
+         Add code for fancy output. This merely silences a compiler-warning
+         and adds about 135 Bytes. May be needed for machines with alot
+         of CPUs.
+
+config TIME
+       bool "time"
+       default n
+       help
+         The time command runs the specified program with the given arguments.
+         When the command finishes, time writes a message to standard output
+         giving timing statistics about this program run.
+
+config 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..51187c5
--- /dev/null
@@ -0,0 +1,33 @@
+# 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_EJECT)       += eject.o
+lib-$(CONFIG_HDPARM)      += hdparm.o
+lib-$(CONFIG_LAST)        += last.o
+lib-$(CONFIG_LESS)        += less.o
+lib-$(CONFIG_MAKEDEVS)    += makedevs.o
+lib-$(CONFIG_MICROCOM)    += microcom.o
+lib-$(CONFIG_MOUNTPOINT)  += mountpoint.o
+lib-$(CONFIG_MT)          += mt.o
+lib-$(CONFIG_RAIDAUTORUN) += raidautorun.o
+lib-$(CONFIG_READAHEAD)   += readahead.o
+lib-$(CONFIG_RUNLEVEL)    += runlevel.o
+lib-$(CONFIG_RX)          += rx.o
+lib-$(CONFIG_SETSID)      += setsid.o
+lib-$(CONFIG_STRINGS)     += strings.o
+lib-$(CONFIG_TASKSET)     += taskset.o
+lib-$(CONFIG_TIME)        += time.o
+lib-$(CONFIG_TTYSIZE)     += ttysize.o
+lib-$(CONFIG_WATCHDOG)    += watchdog.o
diff --git a/miscutils/adjtimex.c b/miscutils/adjtimex.c
new file mode 100644 (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..f3aef42
--- /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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       printf(bbconfig_config);
+       return 0;
+}
diff --git a/miscutils/chat.c b/miscutils/chat.c
new file mode 100644 (file)
index 0000000..64d4ba4
--- /dev/null
@@ -0,0 +1,444 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones chat utility
+ * inspired by ppp's chat
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+/*
+#define ENABLE_FEATURE_CHAT_NOFAIL              1 // +126 bytes
+#define ENABLE_FEATURE_CHAT_TTY_HIFI            0 // + 70 bytes
+#define ENABLE_FEATURE_CHAT_IMPLICIT_CR         1 // + 44 bytes
+#define ENABLE_FEATURE_CHAT_SEND_ESCAPES        0 // +103 bytes
+#define ENABLE_FEATURE_CHAT_VAR_ABORT_LEN       0 // + 70 bytes
+#define ENABLE_FEATURE_CHAT_CLR_ABORT           0 // +113 bytes
+#define ENABLE_FEATURE_CHAT_SWALLOW_OPTS        0 // + 23 bytes
+*/
+
+// default timeout: 45 sec
+#define        DEFAULT_CHAT_TIMEOUT 45*1000
+// max length of "abort string",
+// i.e. device reply which causes termination
+#define MAX_ABORT_LEN 50
+
+// possible exit codes
+enum {
+       ERR_OK = 0,     // all's well
+       ERR_MEM,        // read too much while expecting
+       ERR_IO,         // signalled or I/O error
+       ERR_TIMEOUT,    // timed out while expecting
+       ERR_ABORT,      // first abort condition was met
+//     ERR_ABORT2,     // second abort condition was met
+//     ...
+};
+
+// exit code
+// N.B> 10 bytes for volatile. Why all these signals?!
+static /*volatile*/ smallint exitcode;
+
+// trap for critical signals
+static void signal_handler(ATTRIBUTE_UNUSED 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 ATTRIBUTE_UNUSED, char **argv)
+{
+// should we dump device output? to what fd? by default no.
+// this can be controlled later via ECHO {ON|OFF} chat directive
+//     int echo_fd;
+       bool echo = 0;
+       // collection of device replies which cause unconditional termination
+       llist_t *aborts = NULL;
+       // inactivity period
+       int timeout = DEFAULT_CHAT_TIMEOUT;
+       // maximum length of abort string
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+       size_t max_abort_len = 0;
+#else
+#define max_abort_len MAX_ABORT_LEN
+#endif
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+       struct termios tio0, tio;
+#endif
+       // directive names
+       enum {
+               DIR_HANGUP = 0,
+               DIR_ABORT,
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+               DIR_CLR_ABORT,
+#endif
+               DIR_TIMEOUT,
+               DIR_ECHO,
+               DIR_SAY,
+       };
+
+       // make x* functions fail with correct exitcode
+       xfunc_error_retval = ERR_IO;
+
+       // trap vanilla signals to prevent process from being killed suddenly
+       bb_signals(0
+               + (1 << SIGHUP)
+               + (1 << SIGINT)
+               + (1 << SIGTERM)
+               + (1 << SIGPIPE)
+               , signal_handler);
+
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+       tcgetattr(STDIN_FILENO, &tio);
+       tio0 = tio;
+       cfmakeraw(&tio);
+       tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
+#endif
+
+#if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
+       getopt32(argv, "vVsSE");
+       argv += optind;
+#else
+       argv++; // goto first arg
+#endif
+       // handle chat expect-send pairs
+       while (*argv) {
+               // directive given? process it
+               int key = index_in_strings(
+                       "HANGUP\0" "ABORT\0"
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+                       "CLR_ABORT\0"
+#endif
+                       "TIMEOUT\0" "ECHO\0" "SAY\0"
+                       , *argv
+               );
+               if (key >= 0) {
+                       // cache directive value
+                       char *arg = *++argv;
+                       // ON -> 1, anything else -> 0
+                       bool onoff = !strcmp("ON", arg);
+                       // process directive
+                       if (DIR_HANGUP == key) {
+                               // turn SIGHUP on/off
+                               signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
+                       } else if (DIR_ABORT == key) {
+                               // append the string to abort conditions
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                               size_t len = strlen(arg);
+                               if (len > max_abort_len)
+                                       max_abort_len = len;
+#endif
+                               llist_add_to_end(&aborts, arg);
+#if ENABLE_FEATURE_CHAT_CLR_ABORT
+                       } else if (DIR_CLR_ABORT == key) {
+                               // remove the string from abort conditions
+                               // N.B. gotta refresh maximum length too...
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                               max_abort_len = 0;
+#endif
+                               for (llist_t *l = aborts; l; l = l->link) {
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                                       size_t len = strlen(l->data);
+#endif
+                                       if (!strcmp(arg, l->data)) {
+                                               llist_unlink(&aborts, l);
+                                               continue;
+                                       }
+#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
+                                       if (len > max_abort_len)
+                                               max_abort_len = len;
+#endif
+                               }
+#endif
+                       } else if (DIR_TIMEOUT == key) {
+                               // set new timeout
+                               // -1 means OFF
+                               timeout = atoi(arg) * 1000;
+                               // 0 means default
+                               // >0 means value in msecs
+                               if (!timeout)
+                                       timeout = DEFAULT_CHAT_TIMEOUT;
+                       } else if (DIR_ECHO == key) {
+                               // turn echo on/off
+                               // N.B. echo means dumping output
+                               // from stdin (device) to stderr
+                               echo = onoff;
+//TODO?                                echo_fd = onoff * STDERR_FILENO;
+//TODO?                                echo_fd = xopen(arg, O_WRONLY|O_CREAT|O_TRUNC);
+                       } else if (DIR_SAY == key) {
+                               // just print argument verbatim
+                               fprintf(stderr, arg);
+                       }
+                       // next, please!
+                       argv++;
+               // ordinary expect-send pair!
+               } else {
+                       //-----------------------
+                       // do expect
+                       //-----------------------
+                       int expect_len;
+                       size_t buf_len = 0;
+                       size_t max_len = max_abort_len;
+
+                       struct pollfd pfd;
+#if ENABLE_FEATURE_CHAT_NOFAIL
+                       int nofail = 0;
+#endif
+                       char *expect = *argv++;
+
+                       // sanity check: shall we really expect something?
+                       if (!expect)
+                               goto expect_done;
+
+#if ENABLE_FEATURE_CHAT_NOFAIL
+                       // if expect starts with -
+                       if ('-' == *expect) {
+                               // swallow -
+                               expect++;
+                               // and enter nofail mode
+                               nofail++;
+                       }
+#endif
+
+#ifdef ___TEST___BUF___ // test behaviour with a small buffer
+#      undef COMMON_BUFSIZE
+#      define COMMON_BUFSIZE 6
+#endif
+                       // expand escape sequences in expect
+                       expect_len = unescape(expect, &expect_len /*dummy*/);
+                       if (expect_len > max_len)
+                               max_len = expect_len;
+                       // sanity check:
+                       // we should expect more than nothing but not more than input buffer
+                       // TODO: later we'll get rid of fixed-size buffer
+                       if (!expect_len)
+                               goto expect_done;
+                       if (max_len >= COMMON_BUFSIZE) {
+                               exitcode = ERR_MEM;
+                               goto expect_done;
+                       }
+
+                       // get reply
+                       pfd.fd = STDIN_FILENO;
+                       pfd.events = POLLIN;
+                       while (!exitcode
+                           && poll(&pfd, 1, timeout) > 0
+                           && (pfd.revents & POLLIN)
+                       ) {
+#define buf bb_common_bufsiz1
+                               llist_t *l;
+                               ssize_t delta;
+
+                               // read next char from device
+                               if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
+                                       // dump device output if ECHO ON or RECORD fname
+//TODO?                                        if (echo_fd > 0) {
+//TODO?                                                full_write(echo_fd, buf+buf_len, 1);
+//TODO?                                        }
+                                       if (echo > 0)
+                                               full_write(STDERR_FILENO, buf+buf_len, 1);
+                                       buf_len++;
+                                       // move input frame if we've reached higher bound
+                                       if (buf_len > COMMON_BUFSIZE) {
+                                               memmove(buf, buf+buf_len-max_len, max_len);
+                                               buf_len = max_len;
+                                       }
+                               }
+                               // N.B. rule of thumb: values being looked for can
+                               // be found only at the end of input buffer
+                               // this allows to get rid of strstr() and memmem()
+
+                               // TODO: make expect and abort strings processed uniformly
+                               // abort condition is met? -> bail out
+                               for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
+                                       size_t len = strlen(l->data);
+                                       delta = buf_len-len;
+                                       if (delta >= 0 && !memcmp(buf+delta, l->data, len))
+                                               goto expect_done;
+                               }
+                               exitcode = ERR_OK;
+
+                               // expected reply received? -> goto next command
+                               delta = buf_len - expect_len;
+                               if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
+                                       goto expect_done;
+#undef buf
+                       }
+
+                       // device timed out or unexpected reply received
+                       exitcode = ERR_TIMEOUT;
+ expect_done:
+#if ENABLE_FEATURE_CHAT_NOFAIL
+                       // on success and when in nofail mode
+                       // we should skip following subsend-subexpect pairs
+                       if (nofail) {
+                               if (!exitcode) {
+                                       // find last send before non-dashed expect
+                                       while (*argv && argv[1] && '-' == argv[1][0])
+                                               argv += 2;
+                                       // skip the pair
+                                       // N.B. do we really need this?!
+                                       if (!*argv++ || !*argv++)
+                                               break;
+                               }
+                               // nofail mode also clears all but IO errors (or signals)
+                               if (ERR_IO != exitcode)
+                                       exitcode = ERR_OK;
+                       }
+#endif
+                       // bail out unless we expected successfully
+                       if (exitcode)
+                               break;
+
+                       //-----------------------
+                       // do send
+                       //-----------------------
+                       if (*argv) {
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+                               int nocr = 0; // inhibit terminating command with \r
+#endif
+                               char *loaded = NULL; // loaded command
+                               size_t len;
+                               char *buf = *argv++;
+
+                               // if command starts with @
+                               // load "real" command from file named after @
+                               if ('@' == *buf) {
+                                       // skip the @ and any following white-space
+                                       trim(++buf);
+                                       buf = loaded = xmalloc_open_read_close(buf, NULL);
+                               }
+
+                               // expand escape sequences in command
+                               len = unescape(buf, &nocr);
+
+                               // send command
+#if ENABLE_FEATURE_CHAT_SEND_ESCAPES
+                               pfd.fd = STDOUT_FILENO;
+                               pfd.events = POLLOUT;
+                               while (len && !exitcode
+                                   && poll(&pfd, 1, timeout) > 0
+                                   && (pfd.revents & POLLOUT)
+                               ) {
+                                       // ugly! ugly! ugly!
+                                       // gotta send char by char to achieve this!
+                                       // Brrr...
+                                       // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
+                                       // "\\K" means send BREAK
+                                       char c = *buf;
+                                       if ('\\' == c) {
+                                               c = *++buf;
+                                               if ('d' == c) {
+                                                       sleep(1);
+                                                       len--;
+                                                       continue;
+                                               } else if ('p' == c) {
+                                                       usleep(10000);
+                                                       len--;
+                                                       continue;
+                                               } else if ('K' == c) {
+                                                       tcsendbreak(STDOUT_FILENO, 0);
+                                                       len--;
+                                                       continue;
+                                               } else {
+                                                       buf--;
+                                               }
+                                       }
+                                       if (safe_write(STDOUT_FILENO, buf, 1) > 0) {
+                                               len--;
+                                               buf++;
+                                       } else
+                                               break;
+                               }
+#else
+//                             if (len) {
+                                       alarm(timeout);
+                                       len -= full_write(STDOUT_FILENO, buf, len);
+                                       alarm(0);
+//                             }
+#endif
+
+                               // report I/O error if there still exists at least one non-sent char
+                               if (len)
+                                       exitcode = ERR_IO;
+
+                               // free loaded command (if any)
+                               if (loaded)
+                                       free(loaded);
+#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
+                               // or terminate command with \r (if not inhibited)
+                               else if (!nocr)
+                                       xwrite(STDOUT_FILENO, "\r", 1);
+#endif
+
+                               // bail out unless we sent command successfully
+                               if (exitcode)
+                                       break;
+
+                       }
+               }
+       }
+
+#if ENABLE_FEATURE_CHAT_TTY_HIFI
+       tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
+#endif
+
+       return exitcode;
+}
diff --git a/miscutils/chrt.c b/miscutils/chrt.c
new file mode 100644 (file)
index 0000000..0d55e32
--- /dev/null
@@ -0,0 +1,124 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * chrt - manipulate real-time attributes of a process
+ * Copyright (c) 2006-2007 Bernhard Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sched.h>
+#include <getopt.h> /* optind */
+#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 ATTRIBUTE_UNUSED, 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..ba9cf35
--- /dev/null
@@ -0,0 +1,921 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * crond -d[#] -c <crondir> -f -b
+ *
+ * run as root, but NOT setuid root
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * (version 2.3.2)
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+
+/* glibc frees previous setenv'ed value when we do next setenv()
+ * of the same variable. uclibc does not do this! */
+#if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
+#define SETENV_LEAKS 0
+#else
+#define SETENV_LEAKS 1
+#endif
+
+
+#ifndef CRONTABS
+#define CRONTABS        "/var/spool/cron/crontabs"
+#endif
+#ifndef TMPDIR
+#define TMPDIR          "/var/spool/cron"
+#endif
+#ifndef SENDMAIL
+#define SENDMAIL        "sendmail"
+#endif
+#ifndef SENDMAIL_ARGS
+#define SENDMAIL_ARGS   "-ti", "oem"
+#endif
+#ifndef CRONUPDATE
+#define CRONUPDATE      "cron.update"
+#endif
+#ifndef MAXLINES
+#define MAXLINES        256    /* max lines in non-root crontabs */
+#endif
+
+
+typedef struct CronFile {
+       struct CronFile *cf_Next;
+       struct CronLine *cf_LineBase;
+       char *cf_User;                  /* username                     */
+       smallint cf_Ready;              /* bool: one or more jobs ready */
+       smallint cf_Running;            /* bool: one or more jobs running */
+       smallint cf_Deleted;            /* marked for deletion, ignore  */
+} CronFile;
+
+typedef struct CronLine {
+       struct CronLine *cl_Next;
+       char *cl_Shell;         /* shell command                        */
+       pid_t cl_Pid;           /* running pid, 0, or armed (-1)        */
+#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
+       int cl_MailPos;         /* 'empty file' size                    */
+       smallint cl_MailFlag;   /* running pid is for mail              */
+#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_DEBUG_CROND_OPTION,
+};
+#if ENABLE_DEBUG_CROND_OPTION
+#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 >= LogLevel) {
+               /* Debug mode: all to (non-redirected) stderr, */
+               /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
+               if (!DebugOpt && LogFile) {
+                       /* Otherwise (log to file): we reopen log file at every write: */
+                       int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
+                       if (logfd >= 0)
+                               xmove_fd(logfd, STDERR_FILENO);
+               }
+// TODO: ERR -> error, WARN -> warning, LVL -> info
+               bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
+       }
+       va_end(va);
+       if (ctl[0] & 0x80)
+               exit(20);
+}
+
+int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int crond_main(int argc ATTRIBUTE_UNUSED, 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_DEBUG_CROND_OPTION(":d-l")
+                       ":l+:d+"; /* -l and -d have numeric param */
+       opt = getopt32(argv, "l:L:fbSc:" USE_DEBUG_CROND_OPTION("d:"),
+                       &LogLevel, &LogFile, &CDir
+                       USE_DEBUG_CROND_OPTION(,&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... */
+       setenv("SHELL", DEFAULT_SHELL, 1); /* once, for all future children */
+       crondlog(LVL9 "crond (busybox "BB_VER") started, log level %d", LogLevel);
+       SynchronizeDir();
+
+       /* main loop - synchronize to 1 second after the minute, minimum sleep
+        * of 1 second. */
+       {
+               time_t t1 = time(NULL);
+               time_t t2;
+               long dt;
+               int rescan = 60;
+               int sleep_time = 60;
+
+               write_pidfile("/var/run/crond.pid");
+               for (;;) {
+                       sleep((sleep_time + 1) - (time(NULL) % sleep_time));
+
+                       t2 = time(NULL);
+                       dt = (long)t2 - (long)t1;
+
+                       /*
+                        * The file 'cron.update' is checked to determine new cron
+                        * jobs.  The directory is rescanned once an hour to deal
+                        * with any screwups.
+                        *
+                        * check for disparity.  Disparities over an hour either way
+                        * result in resynchronization.  A reverse-indexed disparity
+                        * less then an hour causes us to effectively sleep until we
+                        * match the original time (i.e. no re-execution of jobs that
+                        * have just been run).  A forward-indexed disparity less then
+                        * an hour causes intermediate jobs to be run, but only once
+                        * in the worst case.
+                        *
+                        * when running jobs, the inequality used is greater but not
+                        * equal to t1, and less then or equal to t2.
+                        */
+                       if (--rescan == 0) {
+                               rescan = 60;
+                               SynchronizeDir();
+                       }
+                       CheckUpdates();
+                       if (DebugOpt)
+                               crondlog(LVL5 "wakeup dt=%ld", dt);
+                       if (dt < -60 * 60 || dt > 60 * 60) {
+                               crondlog(WARN9 "time disparity of %d minutes detected", dt / 60);
+                       } else if (dt > 0) {
+                               TestJobs(t1, t2);
+                               RunJobs();
+                               sleep(5);
+                               if (CheckJobs() > 0) {
+                                       sleep_time = 10;
+                               } else {
+                                       sleep_time = 60;
+                               }
+                       }
+                       t1 = t2;
+               }
+       }
+       return 0; /* not reached */
+}
+
+#if SETENV_LEAKS
+/* We set environment *before* vfork (because we want to use vfork),
+ * so we cannot use setenv() - repeated calls to setenv() may leak memory!
+ * Using putenv(), and freeing memory after unsetenv() won't leak */
+static void safe_setenv4(char **pvar_val, const char *var, const char *val /*, int len*/)
+{
+       const int len = 4; /* both var names are 4 char long */
+       char *var_val = *pvar_val;
+
+       if (var_val) {
+               var_val[len] = '\0'; /* nuke '=' */
+               unsetenv(var_val);
+               free(var_val);
+       }
+       *pvar_val = xasprintf("%s=%s", var, val);
+       putenv(*pvar_val);
+}
+#endif
+
+static void SetEnv(struct passwd *pas)
+{
+#if SETENV_LEAKS
+       safe_setenv4(&env_var_user, "USER", pas->pw_name);
+       safe_setenv4(&env_var_home, "HOME", pas->pw_dir);
+       /* if we want to set user's shell instead: */
+       /*safe_setenv(env_var_user, "SHELL", pas->pw_shell, 5);*/
+#else
+       setenv("USER", pas->pw_name, 1);
+       setenv("HOME", pas->pw_dir, 1);
+#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 char *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;
+
+       if (base == NULL) {
+               return NULL;
+       }
+
+       while (!isspace(*ptr)) {
+               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) {
+                       crondlog(WARN9 "user %s: parse error at %s", user, base);
+                       return NULL;
+               }
+               if (*ptr == '-' && n2 < 0) {
+                       ++ptr;
+                       continue;
+               }
+
+               /*
+                * collapse single-value ranges, handle skipmark, and fill
+                * in the character array appropriately.
+                */
+               if (n2 < 0) {
+                       n2 = n1;
+               }
+               if (*ptr == '/') {
+                       skip = strtol(ptr + 1, &ptr, 10);
+               }
+
+               /*
+                * fill array, using a failsafe is the easiest way to prevent
+                * an endless loop
+                */
+               {
+                       int s0 = 1;
+                       int failsafe = 1024;
+
+                       --n1;
+                       do {
+                               n1 = (n1 + 1) % modvalue;
+
+                               if (--s0 == 0) {
+                                       ary[n1 % modvalue] = 1;
+                                       s0 = skip;
+                               }
+                               if (--failsafe == 0) {
+                                       crondlog(WARN9 "user %s: parse error at %s", user, base);
+                                       return NULL;
+                               }
+                       } while (n1 != n2);
+
+               }
+               if (*ptr != ',') {
+                       break;
+               }
+               ++ptr;
+               n1 = -1;
+               n2 = -1;
+       }
+
+       if (!isspace(*ptr)) {
+               crondlog(WARN9 "user %s: parse error at %s", user, base);
+               return NULL;
+       }
+
+       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);
+       }
+       return skip_whitespace(ptr);
+}
+
+static void FixDayDow(CronLine *line)
+{
+       int 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)
+{
+       FILE *fi;
+       struct stat sbuf;
+       int maxEntries;
+       int maxLines;
+       char buf[1024];
+
+       if (!fileName)
+               return;
+
+       DeleteFile(fileName);
+       fi = fopen(fileName, "r");
+       if (!fi)
+               return;
+
+       maxEntries = MAXLINES;
+       if (strcmp(fileName, "root") == 0) {
+               maxEntries = 65535;
+       }
+       maxLines = maxEntries * 10;
+
+       if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
+               CronFile *file = xzalloc(sizeof(CronFile));
+               CronLine **pline;
+
+               file->cf_User = xstrdup(fileName);
+               pline = &file->cf_LineBase;
+
+               while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
+                       CronLine *line;
+                       char *ptr;
+
+                       trim(buf);
+                       if (buf[0] == '\0' || buf[0] == '#') {
+                               continue;
+                       }
+                       if (--maxEntries == 0) {
+                               break;
+                       }
+                       if (DebugOpt) {
+                               crondlog(LVL5 "user:%s entry:%s", fileName, buf);
+                       }
+                       *pline = line = xzalloc(sizeof(CronLine));
+                       /* parse date ranges */
+                       ptr = ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, buf);
+                       ptr = ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, ptr);
+                       ptr = ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, ptr);
+                       ptr = ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, ptr);
+                       ptr = ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, ptr);
+                       /* check failure */
+                       if (ptr == NULL) {
+                               free(line);
+                               continue;
+                       }
+                       /*
+                        * fix days and dow - if one is not * and the other
+                        * is *, the other is set to 0, and vise-versa
+                        */
+                       FixDayDow(line);
+                       /* copy command */
+                       line->cl_Shell = xstrdup(ptr);
+                       if (DebugOpt) {
+                               crondlog(LVL5 " command:%s", ptr);
+                       }
+                       pline = &line->cl_Next;
+               }
+               *pline = NULL;
+
+               file->cf_Next = FileBase;
+               FileBase = file;
+
+               if (maxLines == 0 || maxEntries == 0) {
+                       crondlog(WARN9 "maximum number of lines reached for user %s", fileName);
+               }
+       }
+       fclose(fi);
+}
+
+static void CheckUpdates(void)
+{
+       FILE *fi;
+       char buf[256];
+
+       fi = fopen(CRONUPDATE, "r");
+       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))) {
+                       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);
+               }
+               execl(prog, prog, cmd, arg, NULL);
+               crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg);
+               if (mail_filename) {
+                       fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
+               }
+               _exit(0);
+       }
+
+       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;
+
+       line->cl_Pid = 0;
+       line->cl_MailFlag = 0;
+
+       /* 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", user,
+                       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;
+       }
+       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);
+               }
+               execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
+               crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user,
+                                DEFAULT_SHELL, "-c", line->cl_Shell);
+               _exit(0);
+       }
+       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..dc3179d
--- /dev/null
@@ -0,0 +1,234 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * CRONTAB
+ *
+ * usually setuid root, -c option only works if getuid() == geteuid()
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+#ifndef CRONTABS
+#define CRONTABS        "/var/spool/cron/crontabs"
+#endif
+#ifndef CRONUPDATE
+#define CRONUPDATE      "cron.update"
+#endif
+
+static void change_user(const struct passwd *pas)
+{
+       setenv("USER", pas->pw_name, 1);
+       setenv("HOME", pas->pw_dir, 1);
+       setenv("SHELL", DEFAULT_SHELL, 1);
+
+       /* 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 ATTRIBUTE_UNUSED, 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 opt_ler;
+
+       /* file [opts]     Replace crontab from file
+        * - [opts]        Replace crontab from stdin
+        * -u user         User
+        * -c dir          Crontab directory
+        * -l              List crontab for user
+        * -e              Edit crontab for user
+        * -r              Delete crontab for user
+        * bbox also supports -d == -r, but most other crontab
+        * implementations do not. Deprecated.
+        */
+       enum {
+               OPT_u = (1 << 0),
+               OPT_c = (1 << 1),
+               OPT_l = (1 << 2),
+               OPT_e = (1 << 3),
+               OPT_r = (1 << 4),
+               OPT_ler = OPT_l + OPT_e + OPT_r,
+       };
+
+       opt_complementary = "?1:dr"; /* max one argument; -d implies -r */
+       opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir);
+       argv += optind;
+
+       if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */
+               /* run by non-root? */
+               if (opt_ler & (OPT_u|OPT_c))
+                       bb_error_msg_and_die("only root can use -c or -u");
+       }
+
+       if (opt_ler & OPT_u) {
+               pas = getpwnam(user_name);
+               if (!pas)
+                       bb_error_msg_and_die("user %s is not known", user_name);
+       } else {
+               uid_t my_uid = getuid();
+               pas = getpwuid(my_uid);
+               if (!pas)
+                       bb_perror_msg_and_die("no user record for UID %u",
+                                       (unsigned)my_uid);
+       }
+
+#define user_name DONT_USE_ME_BEYOND_THIS_POINT
+
+       /* From now on, keep only -l, -e, -r bits */
+       opt_ler &= OPT_ler;
+       if ((opt_ler - 1) & opt_ler) /* more than one bit set? */
+               bb_show_usage();
+
+       /* Read replacement file under user's UID/GID/group vector */
+       if (!opt_ler) { /* Replace? */
+               if (!argv[0])
+                       bb_show_usage();
+               if (NOT_LONE_DASH(argv[0])) {
+                       fd = open_as_user(pas, argv[0]);
+                       if (fd < 0)
+                               bb_error_msg_and_die("user %s cannot read %s",
+                                               pas->pw_name, argv[0]);
+                       xmove_fd(fd, STDIN_FILENO);
+               }
+       }
+
+       /* 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 */
+               fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
+               xmove_fd(fd, STDIN_FILENO);
+               fchown(STDIN_FILENO, pas->pw_uid, pas->pw_gid);
+               fd = open(pas->pw_name, O_RDONLY);
+               if (fd >= 0) {
+                       bb_copyfd_eof(fd, STDIN_FILENO);
+                       close(fd);
+               }
+               edit_file(pas, tmp_fname);
+               xlseek(STDIN_FILENO, 0, SEEK_SET);
+               /* 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(STDIN_FILENO, 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..6129375
--- /dev/null
@@ -0,0 +1,225 @@
+/* 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 { \
+} 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());
+}
+
+static void power(void)
+{
+       double topower = pop();
+
+       push(pow(pop(), topower));
+}
+
+static void divide(void)
+{
+       double divisor = pop();
+
+       push(pop() / divisor);
+}
+
+static void mod(void)
+{
+       unsigned 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)
+{
+       base = (unsigned)pop();
+       if ((base != 10) && (base != 16)) {
+               bb_error_msg("error, base %d is not supported", base);
+               base = 10;
+       }
+}
+
+static void print_base(double print)
+{
+       if (base == 16)
+               printf("%x\n", (unsigned)print);
+       else
+               printf("%g\n", print);
+}
+
+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},
+       {"**",  power},
+       {"exp", power},
+       {"pow", power},
+       {"%",   mod},
+       {"mod", mod},
+       {"and", and},
+       {"or",  or},
+       {"not", not},
+       {"eor", eor},
+       {"xor", eor},
+       {"p", print_no_pop},
+       {"f", print_stack_no_pop},
+       {"o", set_output_base},
+       { /* 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 ATTRIBUTE_UNUSED, 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_getline(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..1b88f05
--- /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(1)
+#define error_logger(p, fmt, args...)
+#define error_logger_and_die(p, fmt, args...)         exit(1)
+#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(0);
+
+       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(path, "r");
+               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 ATTRIBUTE_UNUSED)
+/*  [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/eject.c b/miscutils/eject.c
new file mode 100644 (file)
index 0000000..42e0719
--- /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 }
+       };
+
+       int 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 = (char*)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 ATTRIBUTE_UNUSED, 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/hdparm.c b/miscutils/hdparm.c
new file mode 100644 (file)
index 0000000..ec5ede6
--- /dev/null
@@ -0,0 +1,2070 @@
+/* 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) ATTRIBUTE_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) {
+                       printf("\t");
+                       if (jj) printf("%umin for %sSECURITY ERASE UNIT. ", jj==ERASE_BITS ? 508 : jj<<1, "");
+                       if (kk) printf("%umin for %sSECURITY ERASE UNIT. ", kk==ERASE_BITS ? 508 : kk<<1, "ENHANCED ");
+                       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) {
+               if (id->eide_pio_modes & 1) printf("pio3 ");
+               if (id->eide_pio_modes & 2) printf("pio4 ");
+               if (id->eide_pio_modes &~3) printf("pio? ");
+       }
+       if (id->capability & 1) {
+               if (id->dma_1word | id->dma_mword) {
+                       printf("\n DMA modes:  ");
+                       if (id->dma_1word & 0x100) printf("*");
+                       if (id->dma_1word & 1) printf("sdma0 ");
+                       if (id->dma_1word & 0x200) printf("*");
+                       if (id->dma_1word & 2) printf("sdma1 ");
+                       if (id->dma_1word & 0x400) printf("*");
+                       if (id->dma_1word & 4) printf("sdma2 ");
+                       if (id->dma_1word & 0xf800) printf("*");
+                       if (id->dma_1word & 0xf8) printf("sdma? ");
+                       if (id->dma_mword & 0x100) printf("*");
+                       if (id->dma_mword & 1) printf("mdma0 ");
+                       if (id->dma_mword & 0x200) printf("*");
+                       if (id->dma_mword & 2) printf("mdma1 ");
+                       if (id->dma_mword & 0x400) printf("*");
+                       if (id->dma_mword & 4) printf("mdma2 ");
+                       if (id->dma_mword & 0xf800) printf("*");
+                       if (id->dma_mword & 0xf8) printf("mdma? ");
+               }
+       }
+       if (((id->capability & 8) || (id->field_valid & 2)) && id->field_valid & 4) {
+               printf("\n UDMA modes: ");
+               if (id->dma_ultra & 0x100) printf("*");
+               if (id->dma_ultra & 0x001) printf("udma0 ");
+               if (id->dma_ultra & 0x200) printf("*");
+               if (id->dma_ultra & 0x002) printf("udma1 ");
+               if (id->dma_ultra & 0x400) printf("*");
+               if (id->dma_ultra & 0x004) printf("udma2 ");
+#ifdef __NEW_HD_DRIVE_ID
+               if (id->hw_config & 0x2000) {
+#else /* !__NEW_HD_DRIVE_ID */
+               if (id->word93 & 0x2000) {
+#endif /* __NEW_HD_DRIVE_ID */
+                       if (id->dma_ultra & 0x0800) printf("*");
+                       if (id->dma_ultra & 0x0008) printf("udma3 ");
+                       if (id->dma_ultra & 0x1000) printf("*");
+                       if (id->dma_ultra & 0x0010) printf("udma4 ");
+                       if (id->dma_ultra & 0x2000) printf("*");
+                       if (id->dma_ultra & 0x0020) printf("udma5 ");
+                       if (id->dma_ultra & 0x4000) printf("*");
+                       if (id->dma_ultra & 0x0040) printf("udma6 ");
+                       if (id->dma_ultra & 0x8000) printf("*");
+                       if (id->dma_ultra & 0x0080) printf("udma7 ");
+               }
+       }
+       printf("\n AdvancedPM=%s", (!(id_regs[83] & 8)) ? "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, 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) ATTRIBUTE_NORETURN;
+static void identify_from_stdin(void)
+{
+       uint16_t sbuf[256];
+       unsigned char buf[1280];
+       unsigned char *b = (unsigned char *)buf;
+       int i;
+
+       xread(0, 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/last.c b/miscutils/last.c
new file mode 100644 (file)
index 0000000..de2d2bb
--- /dev/null
@@ -0,0 +1,89 @@
+/* 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>
+
+#ifndef SHUTDOWN_TIME
+#  define SHUTDOWN_TIME 254
+#endif
+
+/* Grr... utmp char[] members do not have to be nul-terminated.
+ * Do what we can while still keeping this reasonably small.
+ * Note: We are assuming the ut_id[] size is fixed at 4. */
+
+#if defined UT_LINESIZE \
+       && ((UT_LINESIZE != 32) || (UT_NAMESIZE != 32) || (UT_HOSTSIZE != 256))
+#error struct utmp member char[] size(s) have changed!
+#elif defined __UT_LINESIZE \
+       && ((__UT_LINESIZE != 32) || (__UT_NAMESIZE != 64) || (__UT_HOSTSIZE != 256))
+#error struct utmp member char[] size(s) have changed!
+#endif
+
+int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int last_main(int argc, char **argv ATTRIBUTE_UNUSED)
+{
+       struct utmp ut;
+       int n, file = STDIN_FILENO;
+       time_t t_tmp;
+
+       if (argc > 1) {
+               bb_show_usage();
+       }
+       file = xopen(bb_path_wtmp_file, O_RDONLY);
+
+       printf("%-10s %-14s %-18s %-12.12s %s\n", "USER", "TTY", "HOST", "LOGIN", "TIME");
+       while ((n = full_read(file, &ut, sizeof(ut))) > 0) {
+               if (n != sizeof(ut)) {
+                       bb_perror_msg_and_die("short read");
+               }
+
+               if (ut.ut_line[0] == '~') {
+                       if (strncmp(ut.ut_user, "shutdown", 8) == 0)
+                               ut.ut_type = SHUTDOWN_TIME;
+                       else if (strncmp(ut.ut_user, "reboot", 6) == 0)
+                               ut.ut_type = BOOT_TIME;
+                       else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
+                               ut.ut_type = RUN_LVL;
+               } else {
+                       if (ut.ut_name[0] == '\0' || strcmp(ut.ut_name, "LOGIN") == 0) {
+                               /* Don't bother.  This means we can't find how long
+                                * someone was logged in for.  Oh well. */
+                               continue;
+                       }
+                       if (ut.ut_type != DEAD_PROCESS
+                        && ut.ut_name[0] && ut.ut_line[0]
+                       ) {
+                               ut.ut_type = USER_PROCESS;
+                       }
+                       if (strcmp(ut.ut_name, "date") == 0) {
+                               if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME;
+                               if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME;
+                       }
+               }
+
+               if (ut.ut_type != USER_PROCESS) {
+                       switch (ut.ut_type) {
+                               case OLD_TIME:
+                               case NEW_TIME:
+                               case RUN_LVL:
+                               case SHUTDOWN_TIME:
+                                       continue;
+                               case BOOT_TIME:
+                                       strcpy(ut.ut_line, "system boot");
+                                       break;
+                       }
+               }
+               t_tmp = (time_t)ut.ut_tv.tv_sec;
+               printf("%-10s %-14s %-18s %-12.12s\n", ut.ut_user, ut.ut_line, ut.ut_host,
+                               ctime(&t_tmp) + 4);
+       }
+
+       fflush_stdout_and_exit(EXIT_SUCCESS);
+}
diff --git a/miscutils/less.c b/miscutils/less.c
new file mode 100644 (file)
index 0000000..37ec5d9
--- /dev/null
@@ -0,0 +1,1409 @@
+/* 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
+
+/* FIXME: currently doesn't work right */
+#undef ENABLE_FEATURE_LESS_FLAGCS
+#define ENABLE_FEATURE_LESS_FLAGCS 0
+
+/* 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"
+
+/* These are the escape sequences corresponding to special keys */
+enum {
+       REAL_KEY_UP = 'A',
+       REAL_KEY_DOWN = 'B',
+       REAL_KEY_RIGHT = 'C',
+       REAL_KEY_LEFT = 'D',
+       REAL_PAGE_UP = '5',
+       REAL_PAGE_DOWN = '6',
+       REAL_KEY_HOME = '7', // vt100? linux vt? or what?
+       REAL_KEY_END = '8',
+       REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
+       REAL_KEY_END_ALT = '4', // ESC [4~
+       REAL_KEY_HOME_XTERM = 'H',
+       REAL_KEY_END_XTERM = 'F',
+
+/* These are the special codes assigned by this program to the special keys */
+       KEY_UP = 20,
+       KEY_DOWN = 21,
+       KEY_RIGHT = 22,
+       KEY_LEFT = 23,
+       PAGE_UP = 24,
+       PAGE_DOWN = 25,
+       KEY_HOME = 26,
+       KEY_END = 27,
+
+/* 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,
+       FLAG_M = 1 << 1,
+       FLAG_m = 1 << 2,
+       FLAG_N = 1 << 3,
+       FLAG_TILDE = 1 << 4,
+/* 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 linepos;
+       unsigned max_displayed_line;
+       unsigned max_fline;
+       unsigned max_lineno; /* this one tracks linewrap */
+       unsigned width;
+       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;
+       struct termios term_orig, term_less;
+};
+#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 linepos             (G.linepos           )
+#define max_displayed_line  (G.max_displayed_line)
+#define max_fline           (G.max_fline         )
+#define max_lineno          (G.max_lineno        )
+#define width               (G.width             )
+#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 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)
+
+/* Reset terminal input to normal */
+static void set_tty_cooked(void)
+{
+       fflush(stdout);
+       tcsetattr(kbd_fd, TCSANOW, &term_orig);
+}
+
+/* Exit the program gracefully */
+static void less_exit(int code)
+{
+       bb_putchar('\n');
+       set_tty_cooked();
+       if (code < 0)
+               kill_myself_with_sig(- code); /* does not return */
+       exit(code);
+}
+
+/* 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);
+}
+
+#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.
+ * linepos - 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 = xmalloc(w);
+       max_fline += last_terminated;
+       if (!last_terminated) {
+               const char *cp = flines[max_fline];
+               if (option_mask32 & FLAG_N)
+                       cp += 8;
+               strcpy(current_line, cp);
+               p += strlen(current_line);
+               free((char*)flines[max_fline]);
+               /* linepos is still valid from previous read_lines() */
+       } else {
+               linepos = 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(0, 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' && linepos && p[-1] != '\t') {
+                               readpos++; /* eat it */
+                               linepos--;
+                       /* was buggy (p could end up <= current_line)... */
+                               *--p = '\0';
+                               continue;
+                       }
+                       {
+                               size_t new_linepos = linepos + 1;
+                               if (c == '\t') {
+                                       new_linepos += 7;
+                                       new_linepos &= (~7);
+                               }
+                               if (new_linepos >= w)
+                                       break;
+                               linepos = new_linepos;
+                       }
+                       /* ok, we will eat this char */
+                       readpos++;
+                       if (c == '\n') {
+                               terminated = 1;
+                               linepos = 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(flines, (max_fline+1) * sizeof(char *));
+               if (option_mask32 & FLAG_N) {
+                       /* Width of 7 preserves tab spacing in the text */
+                       flines[max_fline] = xasprintf(
+                               (max_lineno <= 9999999) ? "%7u %s" : "%07u %s",
+                               max_lineno % 10000000, current_line);
+                       free(current_line);
+                       if (terminated)
+                               max_lineno++;
+               } else {
+                       flines[max_fline] = xrealloc(current_line, strlen(current_line)+1);
+               }
+               if (max_fline >= MAXLINES) {
+                       eof_error = 0; /* Pretend we saw EOF */
+                       break;
+               }
+               if (max_fline > 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 = xmalloc(w);
+               p = current_line;
+               linepos = 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 >= 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 < 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";
+
+#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];
+       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 (!growline) {
+               printf(CLEAR_2_EOL"%s\n", str);
+               return;
+       }
+       printf(CLEAR_2_EOL"%s%s\n", growline, str);
+       free(growline);
+}
+#else
+void print_found(const char *line);
+#endif
+
+static void print_ascii(const char *str)
+{
+       char buf[width];
+       char *p;
+       size_t n;
+
+       printf(CLEAR_2_EOL);
+       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)
+{
+       int 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)
+{
+       int i;
+       for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
+               buffer[i] = flines[cur_fline + i];
+       }
+       for (; i <= max_displayed_line; i++) {
+               buffer[i] = empty_line_marker;
+       }
+       buffer_print();
+}
+
+/* Move the buffer up and down in the file in order to scroll */
+static void buffer_down(int nlines)
+{
+       cur_fline += nlines;
+       read_lines();
+       cap_cur_fline(nlines);
+       buffer_fill_and_print();
+}
+
+static void buffer_up(int nlines)
+{
+       cur_fline -= nlines;
+       if (cur_fline < 0) cur_fline = 0;
+       read_lines();
+       buffer_fill_and_print();
+}
+
+static void buffer_line(int linenum)
+{
+       if (linenum < 0)
+               linenum = 0;
+       cur_fline = linenum;
+       read_lines();
+       if (linenum + max_displayed_line > max_fline)
+               linenum = max_fline - max_displayed_line + TILDES;
+       if (linenum < 0)
+               linenum = 0;
+       cur_fline = linenum;
+       buffer_fill_and_print();
+}
+
+static void open_file_and_read_lines(void)
+{
+       if (filename) {
+               int fd = xopen(filename, O_RDONLY);
+               dup2(fd, 0);
+               if (fd) close(fd);
+       } else {
+               /* "less" with no arguments in argv[] */
+               /* For status line only */
+               filename = xstrdup(bb_msg_standard_input);
+       }
+       readpos = 0;
+       readeof = 0;
+       linepos = 0;
+       terminated = 1;
+       read_lines();
+}
+
+/* Reinitialize everything for a new file - free the memory and start over */
+static void reinitialize(void)
+{
+       int i;
+
+       if (flines) {
+               for (i = 0; i <= max_fline; i++)
+                       free((void*)(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(char* input, int sz)
+{
+       ssize_t 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;
+       if (max_fline <= cur_fline + max_displayed_line
+        && eof_error > 0 /* did NOT reach eof yet */
+       ) {
+               /* We are interested in stdin */
+               rd = 0;
+       }
+       /* position cursor if line input is done */
+       if (less_gets_pos >= 0)
+               move_cursor(max_displayed_line + 2, less_gets_pos + 1);
+       fflush(stdout);
+       safe_poll(pfd + rd, 2 - rd, -1);
+
+       input[0] = '\0';
+       rd = safe_read(kbd_fd, input, sz); /* NB: kbd_fd is in O_NONBLOCK mode */
+       if (rd < 0 && errno == EAGAIN) {
+               /* No keyboard input -> we have input on stdin! */
+               read_lines();
+               buffer_fill_and_print();
+               goto again;
+       }
+       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)
+{
+       unsigned char input[16];
+       unsigned i;
+
+ again:
+       less_gets_pos = pos;
+       memset(input, 0, sizeof(input));
+       getch_nowait(input, sizeof(input));
+       less_gets_pos = -1;
+
+       /* Detect escape sequences (i.e. arrow keys) and handle
+        * them accordingly */
+       if (input[0] == '\033' && input[1] == '[') {
+               i = input[2] - REAL_KEY_UP;
+               if (i < 4)
+                       return 20 + i;
+               i = input[2] - REAL_PAGE_UP;
+               if (i < 4)
+                       return 24 + i;
+               if (input[2] == REAL_KEY_HOME_XTERM)
+                       return KEY_HOME;
+               if (input[2] == REAL_KEY_HOME_ALT)
+                       return KEY_HOME;
+               if (input[2] == REAL_KEY_END_XTERM)
+                       return KEY_END;
+               if (input[2] == REAL_KEY_END_ALT)
+                       return KEY_END;
+               return 0;
+       }
+       /* Reject almost all control chars */
+       i = input[0];
+       if (i < ' ' && i != 0x0d && i != 8)
+               goto again;
+       return i;
+}
+
+static char* less_gets(int sz)
+{
+       char c;
+       int i = 0;
+       char *result = xzalloc(1);
+
+       while (1) {
+               c = '\0';
+               less_gets_pos = sz + i;
+               getch_nowait(&c, 1);
+               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 < ' ')
+                       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)
+{
+       int 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(0);
+               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(match_lines, (num_matches+1) * sizeof(int));
+                       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, 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 (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)
+{
+       int i;
+       int num;
+       char num_input[sizeof(int)*4]; /* more than enough */
+       char keypress;
+
+       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) {
+               num_input[i] = less_getch(i + 1);
+               if (!num_input[i] || !isdigit(num_input[i]))
+                       break;
+               bb_putchar(num_input[i]);
+               i++;
+       }
+
+       /* Take the final letter out of the digits string */
+       keypress = num_input[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 KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
+               buffer_down(num);
+               break;
+       case KEY_UP: case 'b': case 'w': case 'y': case 'u':
+               buffer_up(num);
+               break;
+       case 'g': case '<': case 'G': case '>':
+               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_FLAGCS
+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;
+       }
+}
+
+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
+
+static void save_input_to_file(void)
+{
+       const char *msg = "";
+       char *current_line;
+       int i;
+       FILE *fp;
+
+       print_statusline("Log file: ");
+       current_line = less_gets(sizeof("Log file: ")-1);
+       if (current_line[0]) {
+               fp = fopen(current_line, "w");
+               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)
+{
+       int 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 KEY_DOWN: case 'e': case 'j': case 0x0d:
+               buffer_down(1);
+               break;
+       case KEY_UP: case 'y': case 'k':
+               buffer_up(1);
+               break;
+       case PAGE_DOWN: case ' ': case 'z': case 'f':
+               buffer_down(max_displayed_line + 1);
+               break;
+       case PAGE_UP: 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 KEY_HOME: case 'g': case 'p': case '<': case '%':
+               buffer_line(0);
+               break;
+       case KEY_END: case 'G': case '>':
+               cur_fline = MAXLINES;
+               read_lines();
+               buffer_line(cur_fline);
+               break;
+       case 'q': case 'Q':
+               less_exit(0);
+               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_FLAGCS
+       case '-':
+               flag_change();
+               buffer_print();
+               break;
+       case '_':
+               show_flag_status();
+               break;
+#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);
+}
+
+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~");
+       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);
+       kbd_fd = open(CURRENT_TTY, O_RDONLY);
+       if (kbd_fd < 0)
+               return bb_cat(argv);
+       ndelay_on(kbd_fd);
+
+       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]);
+
+       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;
+
+       buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
+       if (option_mask32 & FLAG_TILDE)
+               empty_line_marker = "";
+
+       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;
+
+       /* We want to restore term_orig on exit */
+       bb_signals(BB_FATAL_SIGS, sig_catcher);
+
+       reinitialize();
+       while (1) {
+               keypress = less_getch(-1); /* -1: do not position cursor */
+               keypress_process(keypress);
+       }
+}
diff --git a/miscutils/makedevs.c b/miscutils/makedevs.c
new file mode 100644 (file)
index 0000000..1f88f34
--- /dev/null
@@ -0,0 +1,231 @@
+/* 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
+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[255];
+       int Smajor, Sminor, S, E;
+
+       if (argc < 7 || *argv[1]=='-')
+               bb_show_usage();
+
+       basedev = argv[1];
+       type = argv[2];
+       Smajor = xatoi_u(argv[3]);
+       Sminor = xatoi_u(argv[4]);
+       S = xatoi_u(argv[5]);
+       E = xatoi_u(argv[6]);
+       nodname = argc == 8 ? basedev : buf;
+
+       mode = 0660;
+
+       switch (type[0]) {
+       case 'c':
+               mode |= S_IFCHR;
+               break;
+       case 'b':
+               mode |= S_IFBLK;
+               break;
+       case 'f':
+               mode |= S_IFIFO;
+               break;
+       default:
+               bb_show_usage();
+       }
+
+       while (S <= E) {
+               int sz;
+
+               sz = snprintf(buf, sizeof(buf), "%s%d", basedev, S);
+               if (sz < 0 || sz >= sizeof(buf))  /* libc different */
+                       bb_error_msg_and_die("%s too large", basedev);
+
+       /* if mode != S_IFCHR and != S_IFBLK third param in mknod() ignored */
+
+               if (mknod(nodname, mode, makedev(Smajor, Sminor)))
+                       bb_error_msg("failed to create: %s", nodname);
+
+               if (nodname == basedev) /* ex. /dev/hda - to /dev/hda1 ... */
+                       nodname = buf;
+               S++;
+               Sminor++;
+       }
+
+       return 0;
+}
+
+#elif 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, char **argv)
+{
+       FILE *table = stdin;
+       char *rootdir = NULL;
+       char *line = NULL;
+       int linenum = 0;
+       int ret = EXIT_SUCCESS;
+
+       getopt32(argv, "d:", &line);
+       if (line)
+               table = xfopen(line, "r");
+
+       if (optind >= argc || (rootdir=argv[optind])==NULL) {
+               bb_error_msg_and_die("root directory not specified");
+       }
+
+       xchdir(rootdir);
+
+       umask(0);
+
+       printf("rootdir=%s\n", rootdir);
+       if (line) {
+               printf("table='%s'\n", line);
+       } else {
+               printf("table=<stdin>\n");
+       }
+
+       while ((line = xmalloc_getline(table))) {
+               char type;
+               unsigned int mode = 0755;
+               unsigned int major = 0;
+               unsigned int minor = 0;
+               unsigned int count = 0;
+               unsigned int increment = 0;
+               unsigned int start = 0;
+               char name[41];
+               char user[41];
+               char group[41];
+               char *full_name;
+               uid_t uid;
+               gid_t gid;
+
+               linenum++;
+
+               if ((2 > sscanf(line, "%40s %c %o %40s %40s %u %u %u %u %u", name,
+                                               &type, &mode, user, group, &major,
+                                               &minor, &start, &increment, &count)) ||
+                               ((major | minor | start | count | increment) > 255))
+               {
+                       if (*line=='\0' || *line=='#' || isspace(*line))
+                               continue;
+                       bb_error_msg("line %d invalid: '%s'", linenum, line);
+                       ret = EXIT_FAILURE;
+                       continue;
+               }
+               if (name[0] == '#') {
+                       continue;
+               }
+
+               gid = (*group) ? get_ug_id(group, xgroup2gid) : getgid();
+               uid = (*user) ? get_ug_id(user, xuname2uid) : getuid();
+               full_name = concat_path_file(rootdir, name);
+
+               if (type == 'd') {
+                       bb_make_directory(full_name, mode | S_IFDIR, FILEUTILS_RECUR);
+                       if (chown(full_name, uid, gid) == -1) {
+                               bb_perror_msg("line %d: chown failed for %s", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               goto loop;
+                       }
+                       if ((mode != -1) && (chmod(full_name, mode) < 0)){
+                               bb_perror_msg("line %d: chmod failed for %s", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               goto loop;
+                       }
+               } else if (type == 'f') {
+                       struct stat st;
+                       if ((stat(full_name, &st) < 0 || !S_ISREG(st.st_mode))) {
+                               bb_perror_msg("line %d: regular file '%s' does not exist", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               goto loop;
+                       }
+                       if (chown(full_name, uid, gid) == -1) {
+                               bb_perror_msg("line %d: chown failed for %s", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               goto loop;
+                       }
+                       if ((mode != -1) && (chmod(full_name, mode) < 0)){
+                               bb_perror_msg("line %d: chmod failed for %s", linenum, full_name);
+                               ret = EXIT_FAILURE;
+                               goto loop;
+                       }
+               } else {
+                       dev_t rdev;
+
+                       if (type == 'p') {
+                               mode |= S_IFIFO;
+                       }
+                       else if (type == 'c') {
+                               mode |= S_IFCHR;
+                       }
+                       else if (type == 'b') {
+                               mode |= S_IFBLK;
+                       } else {
+                               bb_error_msg("line %d: unsupported file type %c", linenum, type);
+                               ret = EXIT_FAILURE;
+                               goto loop;
+                       }
+
+                       if (count > 0) {
+                               int i;
+                               char *full_name_inc;
+
+                               full_name_inc = xmalloc(strlen(full_name) + 4);
+                               for (i = start; i < count; i++) {
+                                       sprintf(full_name_inc, "%s%d", full_name, i);
+                                       rdev = makedev(major, minor + (i * increment - start));
+                                       if (mknod(full_name_inc, mode, rdev) == -1) {
+                                               bb_perror_msg("line %d: cannot create node %s", linenum, full_name_inc);
+                                               ret = EXIT_FAILURE;
+                                       }
+                                       else if (chown(full_name_inc, uid, gid) == -1) {
+                                               bb_perror_msg("line %d: chown failed for %s", linenum, full_name_inc);
+                                               ret = EXIT_FAILURE;
+                                       }
+                                       if ((mode != -1) && (chmod(full_name_inc, mode) < 0)){
+                                               bb_perror_msg("line %d: chmod failed for %s", linenum, full_name_inc);
+                                               ret = EXIT_FAILURE;
+                                       }
+                               }
+                               free(full_name_inc);
+                       } else {
+                               rdev = makedev(major, minor);
+                               if (mknod(full_name, mode, rdev) == -1) {
+                                       bb_perror_msg("line %d: cannot create node %s", linenum, full_name);
+                                       ret = EXIT_FAILURE;
+                               }
+                               else if (chown(full_name, uid, gid) == -1) {
+                                       bb_perror_msg("line %d: chown failed for %s", linenum, full_name);
+                                       ret = EXIT_FAILURE;
+                               }
+                               if ((mode != -1) && (chmod(full_name, mode) < 0)){
+                                       bb_perror_msg("line %d: chmod failed for %s", linenum, full_name);
+                                       ret = EXIT_FAILURE;
+                               }
+                       }
+               }
+loop:
+               free(line);
+               free(full_name);
+       }
+       fclose(table);
+
+       return ret;
+}
+
+#else
+# error makedevs configuration error, either leaf or table must be selected
+#endif
diff --git a/miscutils/microcom.c b/miscutils/microcom.c
new file mode 100644 (file)
index 0000000..5ce430a
--- /dev/null
@@ -0,0 +1,181 @@
+/* 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"
+
+/* All known arches use small ints for signals */
+static volatile smallint signalled;
+
+static void signal_handler(int signo)
+{
+       signalled = signo;
+}
+
+// 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 ATTRIBUTE_UNUSED, 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)
+               , signal_handler);
+
+       // error exit code if we fail to open the device
+       signalled = 1;
+
+       // 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;
+       }
+
+       // 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);
+//     tio.c_cflag |= (CREAD|HUPCL); // we just bail out on any device error
+       // set device speed
+       cfsetspeed(&tio, tty_value_to_baud(speed));
+       if (xset1(sfd, &tio, argv[0]))
+               goto restore0_and_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;
+
+       signalled = 0;
+       nfd = 2;
+       while (!signalled && 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
+                               signalled = SIGHUP;
+                               break;
+                       }
+               }
+       }
+
+       // restore device mode
+       tcsetattr(sfd, TCSAFLUSH, &tiosfd);
+
+restore0_and_done:
+       if (isatty(STDIN_FILENO))
+               tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
+
+done:
+       if (device_lock_file)
+               unlink(device_lock_file);
+
+       return signalled;
+}
diff --git a/miscutils/mountpoint.c b/miscutils/mountpoint.c
new file mode 100644 (file)
index 0000000..5647e4c
--- /dev/null
@@ -0,0 +1,66 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mountpoint implementation for busybox
+ *
+ * Copyright (C) 2005 Bernhard Fischer
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Based on sysvinit's mountpoint
+ */
+
+#include "libbb.h"
+
+int mountpoint_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int mountpoint_main(int argc, char **argv)
+{
+       struct stat st;
+       char *arg;
+       int opt = getopt32(argv, "qdx");
+#define OPT_q (1)
+#define OPT_d (2)
+#define OPT_x (4)
+
+       if (optind != argc - 1)
+               bb_show_usage();
+
+       arg = argv[optind];
+
+       if ( (opt & OPT_x && stat(arg, &st) == 0) || (lstat(arg, &st) == 0) ) {
+               if (opt & OPT_x) {
+                       if (S_ISBLK(st.st_mode)) {
+                               printf("%u:%u\n", major(st.st_rdev),
+                                                       minor(st.st_rdev));
+                               return EXIT_SUCCESS;
+                       } else {
+                               if (opt & OPT_q)
+                                       bb_putchar('\n');
+                               else
+                                       bb_error_msg("%s: not a block device", arg);
+                       }
+                       return EXIT_FAILURE;
+               } else
+               if (S_ISDIR(st.st_mode)) {
+                       dev_t st_dev = st.st_dev;
+                       ino_t st_ino = st.st_ino;
+                       char *p = xasprintf("%s/..", arg);
+
+                       if (stat(p, &st) == 0) {
+                               int ret = (st_dev != st.st_dev) ||
+                                       (st_dev == st.st_dev && st_ino == st.st_ino);
+                               if (opt & OPT_d)
+                                       printf("%u:%u\n", major(st_dev), minor(st_dev));
+                               else if (!(opt & OPT_q))
+                                       printf("%s is %sa mountpoint\n", arg, ret?"":"not ");
+                               return !ret;
+                       }
+               } else {
+                       if (!(opt & OPT_q))
+                               bb_error_msg("%s: not a directory", arg);
+                       return EXIT_FAILURE;
+               }
+       }
+       if (!(opt & OPT_q))
+               bb_simple_perror_msg(arg);
+       return EXIT_FAILURE;
+}
diff --git a/miscutils/mt.c b/miscutils/mt.c
new file mode 100644 (file)
index 0000000..c56a8e0
--- /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 ATTRIBUTE_UNUSED, 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..2766245
--- /dev/null
@@ -0,0 +1,25 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * raidautorun implementation for busybox
+ *
+ * Copyright (C) 2006 Bernhard Fischer
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ */
+
+#include "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..04064ee
--- /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 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..48867b8
--- /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 ATTRIBUTE_UNUSED 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..014de51
--- /dev/null
@@ -0,0 +1,35 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * setsid.c -- execute a command in a new session
+ * Rick Sladkey <jrs@world.std.com>
+ * In the public domain.
+ *
+ * 1999-02-22 Arkadiusz Mickiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2001-01-18 John Fremlin <vii@penguinpowered.com>
+ * - fork in case we are process group leader
+ *
+ * 2004-11-12 Paul Fox
+ * - busyboxed
+ */
+
+#include "libbb.h"
+
+int setsid_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int setsid_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       if (!argv[1])
+               bb_show_usage();
+
+       /* setsid() is allowed only when we are not a process group leader.
+        * Otherwise our PID serves as PGID of some existing process group
+        * and cannot be used as PGID of a new process group. */
+       if (getpgrp() == getpid())
+               forkexit_or_rexec(argv);
+
+       setsid();  /* no error possible */
+
+       BB_EXECVP(argv[1], argv + 1);
+       bb_simple_perror_msg_and_die(argv[1]);
+}
diff --git a/miscutils/strings.c b/miscutils/strings.c
new file mode 100644 (file)
index 0000000..5efbabf
--- /dev/null
@@ -0,0 +1,86 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * strings implementation for busybox
+ *
+ * Copyright Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include <getopt.h>
+
+#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 ATTRIBUTE_UNUSED, char **argv)
+{
+       int n, c, status = EXIT_SUCCESS;
+       unsigned opt;
+       unsigned count;
+       off_t offset;
+       FILE *file;
+       char *string;
+       const char *fmt = "%s: ";
+       const char *n_arg = "4";
+
+       opt = getopt32(argv, "afon:", &n_arg);
+       /* -a is our default behaviour */
+       /*argc -= optind;*/
+       argv += optind;
+
+       n = xatou_range(n_arg, 1, INT_MAX);
+       string = xzalloc(n + 1);
+       n--;
+
+       if (!*argv) {
+               fmt = "{%s}: ";
+               *--argv = (char *)bb_msg_standard_input;
+       }
+
+       do {
+               file = fopen_or_warn_stdin(*argv);
+               if (!file) {
+                       status = EXIT_FAILURE;
+                       continue;
+               }
+               offset = 0;
+               count = 0;
+               do {
+                       c = fgetc(file);
+                       if (isprint(c) || c == '\t') {
+                               if (count > n) {
+                                       bb_putchar(c);
+                               } else {
+                                       string[count] = c;
+                                       if (count == n) {
+                                               if (opt & PRINT_NAME) {
+                                                       printf(fmt, *argv);
+                                               }
+                                               if (opt & PRINT_OFFSET) {
+                                                       printf("%7"OFF_FMT"o ", offset - n);
+                                               }
+                                               fputs(string, stdout);
+                                       }
+                                       count++;
+                               }
+                       } else {
+                               if (count > n) {
+                                       bb_putchar('\n');
+                               }
+                               count = 0;
+                       }
+                       offset++;
+               } while (c != EOF);
+               fclose_if_not_stdin(file);
+       } while (*++argv);
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(string);
+
+       fflush_stdout_and_exit(status);
+}
diff --git a/miscutils/taskset.c b/miscutils/taskset.c
new file mode 100644 (file)
index 0000000..f23b7ff
--- /dev/null
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * taskset - retrieve or set a processes' CPU affinity
+ * Copyright (c) 2006 Bernhard Fischer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sched.h>
+#include <getopt.h> /* optind */
+#include "libbb.h"
+
+#if ENABLE_FEATURE_TASKSET_FANCY
+#define TASKSET_PRINTF_MASK "%s"
+#define from_cpuset(x) __from_cpuset(&x)
+/* craft a string from the mask */
+static char *__from_cpuset(cpu_set_t *mask)
+{
+       int i;
+       char *ret = 0, *str = xzalloc(9);
+
+       for (i = CPU_SETSIZE - 4; i >= 0; i -= 4) {
+               char val = 0;
+               int off;
+               for (off = 0; off <= 3; ++off)
+                       if (CPU_ISSET(i+off, mask))
+                               val |= 1<<off;
+
+               if (!ret && val)
+                       ret = str;
+               *str++ = (val-'0'<=9) ? (val+48) : (val+87);
+       }
+       return ret;
+}
+#else
+#define TASKSET_PRINTF_MASK "%x"
+/* (void*) cast is for battling gcc: */
+/* "dereferencing type-punned pointer will break strict-aliasing rules" */
+#define from_cpuset(mask) (*(unsigned*)(void*)&(mask))
+#endif
+
+
+int taskset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int taskset_main(int argc ATTRIBUTE_UNUSED, 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..ed43859
--- /dev/null
@@ -0,0 +1,428 @@
+/* vi: set sw=4 ts=4: */
+/* 'time' utility to display resource usage of processes.
+   Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc.
+
+   Licensed under GPL version 2, see file LICENSE in this tarball for details.
+*/
+/* Originally written by David Keppel <pardo@cs.washington.edu>.
+   Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>.
+   Heavily modified for busybox by Erik Andersen <andersen@codepoet.org>
+*/
+
+#include "libbb.h"
+
+/* Information on the resources used by a child process.  */
+typedef struct {
+       int waitstatus;
+       struct rusage ru;
+       unsigned elapsed_ms;    /* Wallclock time of process.  */
+} resource_t;
+
+/* msec = milliseconds = 1/1,000 (1*10e-3) second.
+   usec = microseconds = 1/1,000,000 (1*10e-6) second.  */
+
+#define UL unsigned long
+
+static const char default_format[] ALIGN1 = "real\t%E\nuser\t%u\nsys\t%T";
+
+/* The output format for the -p option .*/
+static const char posix_format[] ALIGN1 = "real %e\nuser %U\nsys %S";
+
+/* Format string for printing all statistics verbosely.
+   Keep this output to 24 lines so users on terminals can see it all.*/
+static const char long_format[] ALIGN1 =
+       "\tCommand being timed: \"%C\"\n"
+       "\tUser time (seconds): %U\n"
+       "\tSystem time (seconds): %S\n"
+       "\tPercent of CPU this job got: %P\n"
+       "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
+       "\tAverage shared text size (kbytes): %X\n"
+       "\tAverage unshared data size (kbytes): %D\n"
+       "\tAverage stack size (kbytes): %p\n"
+       "\tAverage total size (kbytes): %K\n"
+       "\tMaximum resident set size (kbytes): %M\n"
+       "\tAverage resident set size (kbytes): %t\n"
+       "\tMajor (requiring I/O) page faults: %F\n"
+       "\tMinor (reclaiming a frame) page faults: %R\n"
+       "\tVoluntary context switches: %w\n"
+       "\tInvoluntary context switches: %c\n"
+       "\tSwaps: %W\n"
+       "\tFile system inputs: %I\n"
+       "\tFile system outputs: %O\n"
+       "\tSocket messages sent: %s\n"
+       "\tSocket messages received: %r\n"
+       "\tSignals delivered: %k\n"
+       "\tPage size (bytes): %Z\n"
+       "\tExit status: %x";
+
+/* Wait for and fill in data on child process PID.
+   Return 0 on error, 1 if ok.  */
+/* pid_t is short on BSDI, so don't try to promote it.  */
+static void resuse_end(pid_t pid, resource_t *resp)
+{
+       pid_t caught;
+
+       /* Ignore signals, but don't ignore the children.  When wait3
+          returns the child process, set the time the command finished. */
+       while ((caught = wait3(&resp->waitstatus, 0, &resp->ru)) != pid) {
+               if (caught == -1 && errno != EINTR) {
+                       bb_perror_msg("wait");
+                       return;
+               }
+       }
+       resp->elapsed_ms = (monotonic_us() / 1000) - resp->elapsed_ms;
+}
+
+static void printargv(char *const *argv)
+{
+       const char *fmt = " %s" + 1;
+       do {
+               printf(fmt, *argv);
+               fmt = " %s";
+       } while (*++argv);
+}
+
+/* Return the number of kilobytes corresponding to a number of pages PAGES.
+   (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
+
+   Try to do arithmetic so that the risk of overflow errors is minimized.
+   This is funky since the pagesize could be less than 1K.
+   Note: Some machines express getrusage statistics in terms of K,
+   others in terms of pages.  */
+static unsigned long ptok(unsigned pagesize, unsigned long pages)
+{
+       unsigned long tmp;
+
+       /* Conversion.  */
+       if (pages > (LONG_MAX / pagesize)) { /* Could overflow.  */
+               tmp = pages / 1024;     /* Smaller first, */
+               return tmp * pagesize;  /* then larger.  */
+       }
+       /* Could underflow.  */
+       tmp = pages * pagesize; /* Larger first, */
+       return tmp / 1024;      /* then smaller.  */
+}
+
+/* summarize: Report on the system use of a command.
+
+   Print the FMT argument except that `%' sequences
+   have special meaning, and `\n' and `\t' are translated into
+   newline and tab, respectively, and `\\' is translated into `\'.
+
+   The character following a `%' can be:
+   (* means the tcsh time builtin also recognizes it)
+   % == a literal `%'
+   C == command name and arguments
+*  D == average unshared data size in K (ru_idrss+ru_isrss)
+*  E == elapsed real (wall clock) time in [hour:]min:sec
+*  F == major page faults (required physical I/O) (ru_majflt)
+*  I == file system inputs (ru_inblock)
+*  K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
+*  M == maximum resident set size in K (ru_maxrss)
+*  O == file system outputs (ru_oublock)
+*  P == percent of CPU this job got (total cpu time / elapsed time)
+*  R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
+*  S == system (kernel) time (seconds) (ru_stime)
+*  T == system time in [hour:]min:sec
+*  U == user time (seconds) (ru_utime)
+*  u == user time in [hour:]min:sec
+*  W == times swapped out (ru_nswap)
+*  X == average amount of shared text in K (ru_ixrss)
+   Z == page size
+*  c == involuntary context switches (ru_nivcsw)
+   e == elapsed real time in seconds
+*  k == signals delivered (ru_nsignals)
+   p == average unshared stack size in K (ru_isrss)
+*  r == socket messages received (ru_msgrcv)
+*  s == socket messages sent (ru_msgsnd)
+   t == average resident set size in K (ru_idrss)
+*  w == voluntary context switches (ru_nvcsw)
+   x == exit status of command
+
+   Various memory usages are found by converting from page-seconds
+   to kbytes by multiplying by the page size, dividing by 1024,
+   and dividing by elapsed real time.
+
+   FMT is the format string, interpreted as described above.
+   COMMAND is the command and args that are being summarized.
+   RESP is resource information on the command.  */
+
+#ifndef TICKS_PER_SEC
+#define TICKS_PER_SEC 100
+#endif
+
+static void summarize(const char *fmt, char **command, resource_t *resp)
+{
+       unsigned vv_ms;     /* Elapsed virtual (CPU) milliseconds */
+       unsigned cpu_ticks; /* Same, in "CPU ticks" */
+       unsigned pagesize = getpagesize();
+
+       /* Impossible: we do not use WUNTRACED flag in wait()...
+       if (WIFSTOPPED(resp->waitstatus))
+               printf("Command stopped by signal %u\n",
+                               WSTOPSIG(resp->waitstatus));
+       else */
+       if (WIFSIGNALED(resp->waitstatus))
+               printf("Command terminated by signal %u\n",
+                               WTERMSIG(resp->waitstatus));
+       else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
+               printf("Command exited with non-zero status %u\n",
+                               WEXITSTATUS(resp->waitstatus));
+
+       vv_ms = (resp->ru.ru_utime.tv_sec + resp->ru.ru_stime.tv_sec) * 1000
+             + (resp->ru.ru_utime.tv_usec + resp->ru.ru_stime.tv_usec) / 1000;
+
+#if (1000 / TICKS_PER_SEC) * TICKS_PER_SEC == 1000
+       /* 1000 is exactly divisible by TICKS_PER_SEC (typical) */
+       cpu_ticks = vv_ms / (1000 / TICKS_PER_SEC);
+#else
+       cpu_ticks = vv_ms * (unsigned long long)TICKS_PER_SEC / 1000;
+#endif
+       if (!cpu_ticks) cpu_ticks = 1; /* we divide by it, must be nonzero */
+
+       while (*fmt) {
+               /* Handle leading literal part */
+               int n = strcspn(fmt, "%\\");
+               if (n) {
+                       printf("%.*s", n, fmt);
+                       fmt += n;
+                       continue;
+               }
+
+               switch (*fmt) {
+#ifdef NOT_NEEDED
+               /* Handle literal char */
+               /* Usually we optimize for size, but there is a limit
+                * for everything. With this we do a lot of 1-byte writes */
+               default:
+                       bb_putchar(*fmt);
+                       break;
+#endif
+
+               case '%':
+                       switch (*++fmt) {
+#ifdef NOT_NEEDED_YET
+               /* Our format strings do not have these */
+               /* and we do not take format str from user */
+                       default:
+                               bb_putchar('%');
+                               /*FALLTHROUGH*/
+                       case '%':
+                               if (!*fmt) goto ret;
+                               bb_putchar(*fmt);
+                               break;
+#endif
+                       case 'C':       /* The command that got timed.  */
+                               printargv(command);
+                               break;
+                       case 'D':       /* Average unshared data size.  */
+                               printf("%lu",
+                                       (ptok(pagesize, (UL) resp->ru.ru_idrss) +
+                                        ptok(pagesize, (UL) resp->ru.ru_isrss)) / cpu_ticks);
+                               break;
+                       case 'E': {     /* Elapsed real (wall clock) time.  */
+                               unsigned seconds = resp->elapsed_ms / 1000;
+                               if (seconds >= 3600)    /* One hour -> h:m:s.  */
+                                       printf("%uh %um %02us",
+                                                       seconds / 3600,
+                                                       (seconds % 3600) / 60,
+                                                       seconds % 60);
+                               else
+                                       printf("%um %u.%02us",  /* -> m:s.  */
+                                                       seconds / 60,
+                                                       seconds % 60,
+                                                       (unsigned)(resp->elapsed_ms / 10) % 100);
+                               break;
+                       }
+                       case 'F':       /* Major page faults.  */
+                               printf("%lu", resp->ru.ru_majflt);
+                               break;
+                       case 'I':       /* Inputs.  */
+                               printf("%lu", resp->ru.ru_inblock);
+                               break;
+                       case 'K':       /* Average mem usage == data+stack+text.  */
+                               printf("%lu",
+                                       (ptok(pagesize, (UL) resp->ru.ru_idrss) +
+                                        ptok(pagesize, (UL) resp->ru.ru_isrss) +
+                                        ptok(pagesize, (UL) resp->ru.ru_ixrss)) / cpu_ticks);
+                               break;
+                       case 'M':       /* Maximum resident set size.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_maxrss));
+                               break;
+                       case 'O':       /* Outputs.  */
+                               printf("%lu", resp->ru.ru_oublock);
+                               break;
+                       case 'P':       /* Percent of CPU this job got.  */
+                               /* % cpu is (total cpu time)/(elapsed time).  */
+                               if (resp->elapsed_ms > 0)
+                                       printf("%u%%", (unsigned)(vv_ms * 100 / resp->elapsed_ms));
+                               else
+                                       printf("?%%");
+                               break;
+                       case 'R':       /* Minor page faults (reclaims).  */
+                               printf("%lu", resp->ru.ru_minflt);
+                               break;
+                       case 'S':       /* System time.  */
+                               printf("%u.%02u",
+                                               (unsigned)resp->ru.ru_stime.tv_sec,
+                                               (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
+                               break;
+                       case 'T':       /* System time.  */
+                               if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s.  */
+                                       printf("%uh %um %02us",
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec / 3600),
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec % 3600) / 60,
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec % 60));
+                               else
+                                       printf("%um %u.%02us",  /* -> m:s.  */
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec / 60),
+                                                       (unsigned)(resp->ru.ru_stime.tv_sec % 60),
+                                                       (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
+                               break;
+                       case 'U':       /* User time.  */
+                               printf("%u.%02u",
+                                               (unsigned)resp->ru.ru_utime.tv_sec,
+                                               (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
+                               break;
+                       case 'u':       /* User time.  */
+                               if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s.  */
+                                       printf("%uh %um %02us",
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec / 3600),
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec % 3600) / 60,
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec % 60));
+                               else
+                                       printf("%um %u.%02us",  /* -> m:s.  */
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec / 60),
+                                                       (unsigned)(resp->ru.ru_utime.tv_sec % 60),
+                                                       (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
+                               break;
+                       case 'W':       /* Times swapped out.  */
+                               printf("%lu", resp->ru.ru_nswap);
+                               break;
+                       case 'X':       /* Average shared text size.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_ixrss) / cpu_ticks);
+                               break;
+                       case 'Z':       /* Page size.  */
+                               printf("%u", getpagesize());
+                               break;
+                       case 'c':       /* Involuntary context switches.  */
+                               printf("%lu", resp->ru.ru_nivcsw);
+                               break;
+                       case 'e':       /* Elapsed real time in seconds.  */
+                               printf("%u.%02u",
+                                               (unsigned)resp->elapsed_ms / 1000,
+                                               (unsigned)(resp->elapsed_ms / 10) % 100);
+                               break;
+                       case 'k':       /* Signals delivered.  */
+                               printf("%lu", resp->ru.ru_nsignals);
+                               break;
+                       case 'p':       /* Average stack segment.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_isrss) / cpu_ticks);
+                               break;
+                       case 'r':       /* Incoming socket messages received.  */
+                               printf("%lu", resp->ru.ru_msgrcv);
+                               break;
+                       case 's':       /* Outgoing socket messages sent.  */
+                               printf("%lu", resp->ru.ru_msgsnd);
+                               break;
+                       case 't':       /* Average resident set size.  */
+                               printf("%lu", ptok(pagesize, (UL) resp->ru.ru_idrss) / cpu_ticks);
+                               break;
+                       case 'w':       /* Voluntary context switches.  */
+                               printf("%lu", resp->ru.ru_nvcsw);
+                               break;
+                       case 'x':       /* Exit status.  */
+                               printf("%u", WEXITSTATUS(resp->waitstatus));
+                               break;
+                       }
+                       break;
+
+#ifdef NOT_NEEDED_YET
+               case '\\':              /* Format escape.  */
+                       switch (*++fmt) {
+                       default:
+                               bb_putchar('\\');
+                               /*FALLTHROUGH*/
+                       case '\\':
+                               if (!*fmt) goto ret;
+                               bb_putchar(*fmt);
+                               break;
+                       case 't':
+                               bb_putchar('\t');
+                               break;
+                       case 'n':
+                               bb_putchar('\n');
+                               break;
+                       }
+                       break;
+#endif
+               }
+               ++fmt;
+       }
+ /* ret: */
+       bb_putchar('\n');
+}
+
+/* Run command CMD and return statistics on it.
+   Put the statistics in *RESP.  */
+static void run_command(char *const *cmd, resource_t *resp)
+{
+       pid_t pid;                      /* Pid of child.  */
+       void (*interrupt_signal)(int);
+       void (*quit_signal)(int);
+
+       resp->elapsed_ms = monotonic_us() / 1000;
+       pid = vfork();          /* Run CMD as child process.  */
+       if (pid < 0)
+               bb_error_msg_and_die("cannot 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       resource_t res;
+       const char *output_format = default_format;
+       int opt;
+
+       /* "+": 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(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..a5061f5
--- /dev/null
@@ -0,0 +1,71 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini watchdog implementation for busybox
+ *
+ * Copyright (C) 2003  Paul Mundt <lethal@linux-sh.org>
+ * Copyright (C) 2006  Bernhard Fischer <busybox@busybox.net>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+#define OPT_FOREGROUND 0x01
+#define OPT_TIMER      0x02
+
+static void watchdog_shutdown(int sig ATTRIBUTE_UNUSED)
+{
+       static const char V = 'V';
+
+       write(3, &V, 1);        /* Magic, see watchdog-api.txt in kernel */
+       if (ENABLE_FEATURE_CLEAN_UP)
+               close(3);
+       exit(0);
+}
+
+int watchdog_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int watchdog_main(int argc, char **argv)
+{
+       unsigned opts;
+       unsigned timer_duration = 30000; /* Userspace timer duration, in milliseconds */
+       char *t_arg;
+
+       opt_complementary = "=1"; /* must have 1 argument */
+       opts = getopt32(argv, "Ft:", &t_arg);
+
+       if (opts & OPT_TIMER) {
+               static const struct suffix_mult suffixes[] = {
+                       { "ms", 1 },
+                       { "", 1000 },
+                       { }
+               };
+               timer_duration = xatou_sfx(t_arg, suffixes);
+       }
+
+       if (!(opts & OPT_FOREGROUND)) {
+               bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+       }
+
+       bb_signals(BB_FATAL_SIGS, watchdog_shutdown);
+
+       /* Use known fd # - avoid needing global 'int fd' */
+       xmove_fd(xopen(argv[argc - 1], O_WRONLY), 3);
+
+// TODO?
+//     if (!(opts & OPT_TIMER)) {
+//             if (ioctl(fd, WDIOC_GETTIMEOUT, &timer_duration) == 0)
+//                     timer_duration *= 500;
+//             else
+//                     timer_duration = 30000;
+//     }
+
+       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(timer_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..364ec24
--- /dev/null
@@ -0,0 +1,158 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Linux Module Utilities"
+
+config INSMOD
+       bool "insmod"
+       default n
+       help
+         insmod is used to load specified modules in the running kernel.
+
+config FEATURE_INSMOD_VERSION_CHECKING
+       bool "Module version checking"
+       default n
+       depends on INSMOD && FEATURE_2_4_MODULES
+       help
+         Support checking of versions for modules.  This is used to
+         ensure that the kernel and module are made for each other.
+
+config FEATURE_INSMOD_KSYMOOPS_SYMBOLS
+       bool "Add module symbols to kernel symbol table"
+       default n
+       depends on INSMOD && FEATURE_2_4_MODULES
+       help
+         By adding module symbols to the kernel symbol table, Oops messages
+         occuring within kernel modules can be properly debugged.  By enabling
+         this feature, module symbols will always be added to the kernel symbol
+         table for properly debugging support.  If you are not interested in
+         Oops messages from kernel modules, say N.
+
+config FEATURE_INSMOD_LOADINKMEM
+       bool "In kernel memory optimization (uClinux only)"
+       default n
+       depends on INSMOD && FEATURE_2_4_MODULES
+       help
+         This is a special uClinux only memory optimization that lets insmod
+         load the specified kernel module directly into kernel space, reducing
+         memory usage by preventing the need for two copies of the module
+         being loaded into memory.
+
+config FEATURE_INSMOD_LOAD_MAP
+       bool "Enable load map (-m) option"
+       default n
+       depends on INSMOD && ( FEATURE_2_4_MODULES || FEATURE_2_6_MODULES )
+       help
+         Enabling this, one would be able to get a load map
+         output on stdout. This makes kernel module debugging
+         easier.
+         If you don't plan to debug kernel modules, you
+         don't need this option.
+
+config FEATURE_INSMOD_LOAD_MAP_FULL
+       bool "Symbols in load map"
+       default y
+       depends on FEATURE_INSMOD_LOAD_MAP
+       help
+         Without this option, -m will only output section
+         load map.  With this option, -m will also output
+         symbols load map.
+
+config RMMOD
+       bool "rmmod"
+       default n
+       help
+         rmmod is used to unload specified modules from the kernel.
+
+config LSMOD
+       bool "lsmod"
+       default n
+       help
+         lsmod is used to display a list of loaded modules.
+
+config FEATURE_LSMOD_PRETTY_2_6_OUTPUT
+       bool "Pretty output for 2.6.x Linux kernels"
+       default n
+       depends on LSMOD
+       help
+         This option makes output format of lsmod adjusted to
+         the format of module-init-tools for Linux kernel 2.6.
+
+config MODPROBE
+       bool "modprobe"
+       default n
+       help
+         Handle the loading of modules, and their dependencies on a high
+         level.
+
+         Note that in the state, modprobe does not understand multiple
+         module options from the configuration file. See option below.
+
+config FEATURE_MODPROBE_MULTIPLE_OPTIONS
+       bool
+       prompt "Multiple options parsing" if NITPICK
+       default y
+       depends on MODPROBE
+       help
+         Allow modprobe to understand more than one option to pass to
+         modules.
+
+         This is a WIP, while waiting for a common argument parsing
+         common amongst all BB applets (shell, modprobe, etc...) and
+         adds around 600 bytes on x86, 700 bytes on ARM. The code is
+         biggish and uggly, but just works.
+
+         Saying Y here is not a bad idea if you're not that short
+         on storage capacity.
+
+config FEATURE_MODPROBE_FANCY_ALIAS
+       bool
+       prompt "Fancy alias parsing" if NITPICK
+       default y
+       depends on MODPROBE && FEATURE_2_6_MODULES
+       help
+         Say 'y' here to enable parsing of aliases with underscore/dash
+         mismatch between module name and file name, along with bus-specific
+         aliases (such as pci:... or usb:... aliases).
+
+comment "Options common to multiple modutils"
+       depends on INSMOD || RMMOD || MODPROBE || LSMOD
+
+config FEATURE_CHECK_TAINTED_MODULE
+       # Simulate indentation
+       bool "Support tainted module checking with new kernels"
+       default y
+       depends on INSMOD || LSMOD
+       help
+         Support checking for tainted modules.  These are usually binary
+         only modules that will make the linux-kernel list ignore your
+         support request.
+         This option is required to support GPLONLY modules.
+
+config FEATURE_2_4_MODULES
+       # Simulate indentation
+       bool "Support version 2.2.x to 2.4.x Linux kernels"
+       default y
+       depends on INSMOD || RMMOD || MODPROBE
+       help
+         Support module loading for 2.2.x and 2.4.x Linux kernels.
+
+config FEATURE_2_6_MODULES
+       # Simulate indentation
+       bool "Support version 2.6.x Linux kernels"
+       default y
+       depends on INSMOD || RMMOD || MODPROBE
+       help
+         Support module loading for newer 2.6.x Linux kernels.
+
+
+config FEATURE_QUERY_MODULE_INTERFACE
+       bool
+       default y
+       depends on FEATURE_2_4_MODULES && !FEATURE_2_6_MODULES
+
+
+endmenu
+
diff --git a/modutils/Kbuild b/modutils/Kbuild
new file mode 100644 (file)
index 0000000..cff02b4
--- /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_INSMOD)           += insmod.o
+lib-$(CONFIG_LSMOD)            += lsmod.o
+lib-$(CONFIG_MODPROBE)         += modprobe.o
+lib-$(CONFIG_RMMOD)            += rmmod.o
diff --git a/modutils/insmod.c b/modutils/insmod.c
new file mode 100644 (file)
index 0000000..6274a8d
--- /dev/null
@@ -0,0 +1,4256 @@
+/* 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 <libgen.h>
+#include <sys/utsname.h>
+
+#if !ENABLE_FEATURE_2_4_MODULES && !ENABLE_FEATURE_2_6_MODULES
+#undef ENABLE_FEATURE_2_4_MODULES
+#define ENABLE_FEATURE_2_4_MODULES 1
+#endif
+
+/*
+ * Big piece of 2.4-specific code
+ */
+#if ENABLE_FEATURE_2_4_MODULES
+
+#if ENABLE_FEATURE_2_6_MODULES
+static int insmod_ng_main(int argc, char **argv);
+#endif
+
+#if ENABLE_FEATURE_INSMOD_LOADINKMEM
+#define LOADBITS 0
+#else
+#define LOADBITS 1
+#endif
+
+/* Alpha */
+#if defined(__alpha__)
+#define MATCH_MACHINE(x) (x == EM_ALPHA)
+#define SHT_RELM       SHT_RELA
+#define Elf64_RelM     Elf64_Rela
+#define ELFCLASSM      ELFCLASS64
+#endif
+
+/* ARM support */
+#if defined(__arm__)
+#define MATCH_MACHINE(x) (x == EM_ARM)
+#define SHT_RELM       SHT_REL
+#define Elf32_RelM     Elf32_Rel
+#define ELFCLASSM      ELFCLASS32
+#define USE_PLT_ENTRIES
+#define PLT_ENTRY_SIZE 8
+#define USE_GOT_ENTRIES
+#define GOT_ENTRY_SIZE 8
+#define USE_SINGLE
+#endif
+
+/* blackfin */
+#if defined(BFIN)
+#define MATCH_MACHINE(x) (x == EM_BLACKFIN)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#endif
+
+/* CRIS */
+#if defined(__cris__)
+#define MATCH_MACHINE(x) (x == EM_CRIS)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#ifndef EM_CRIS
+#define EM_CRIS 76
+#define R_CRIS_NONE 0
+#define R_CRIS_32   3
+#endif
+#endif
+
+/* H8/300 */
+#if defined(__H8300H__) || defined(__H8300S__)
+#define MATCH_MACHINE(x) (x == EM_H8_300)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#define USE_SINGLE
+#define SYMBOL_PREFIX  "_"
+#endif
+
+/* PA-RISC / HP-PA */
+#if defined(__hppa__)
+#define MATCH_MACHINE(x) (x == EM_PARISC)
+#define SHT_RELM       SHT_RELA
+#if defined(__LP64__)
+#define Elf64_RelM     Elf64_Rela
+#define ELFCLASSM      ELFCLASS64
+#else
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#endif
+#endif
+
+/* x86 */
+#if defined(__i386__)
+#ifndef EM_486
+#define MATCH_MACHINE(x) (x == EM_386)
+#else
+#define MATCH_MACHINE(x) (x == EM_386 || x == EM_486)
+#endif
+#define SHT_RELM       SHT_REL
+#define Elf32_RelM     Elf32_Rel
+#define ELFCLASSM      ELFCLASS32
+#define USE_GOT_ENTRIES
+#define GOT_ENTRY_SIZE 4
+#define USE_SINGLE
+#endif
+
+/* IA64, aka Itanium */
+#if defined(__ia64__)
+#define MATCH_MACHINE(x) (x == EM_IA_64)
+#define SHT_RELM       SHT_RELA
+#define Elf64_RelM     Elf64_Rela
+#define ELFCLASSM      ELFCLASS64
+#endif
+
+/* m68k */
+#if defined(__mc68000__)
+#define MATCH_MACHINE(x) (x == EM_68K)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#define USE_GOT_ENTRIES
+#define GOT_ENTRY_SIZE 4
+#define USE_SINGLE
+#endif
+
+/* Microblaze */
+#if defined(__microblaze__)
+#define USE_SINGLE
+#define MATCH_MACHINE(x) (x == EM_XILINX_MICROBLAZE)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#endif
+
+/* MIPS */
+#if defined(__mips__)
+#define MATCH_MACHINE(x) (x == EM_MIPS || x == EM_MIPS_RS3_LE)
+#define SHT_RELM       SHT_REL
+#define Elf32_RelM     Elf32_Rel
+#define ELFCLASSM      ELFCLASS32
+/* Account for ELF spec changes.  */
+#ifndef EM_MIPS_RS3_LE
+#ifdef EM_MIPS_RS4_BE
+#define EM_MIPS_RS3_LE EM_MIPS_RS4_BE
+#else
+#define EM_MIPS_RS3_LE 10
+#endif
+#endif /* !EM_MIPS_RS3_LE */
+#define ARCHDATAM       "__dbe_table"
+#endif
+
+/* Nios II */
+#if defined(__nios2__)
+#define MATCH_MACHINE(x) (x == EM_ALTERA_NIOS2)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#endif
+
+/* PowerPC */
+#if defined(__powerpc64__)
+#define MATCH_MACHINE(x) (x == EM_PPC64)
+#define SHT_RELM       SHT_RELA
+#define Elf64_RelM     Elf64_Rela
+#define ELFCLASSM      ELFCLASS64
+#elif defined(__powerpc__)
+#define MATCH_MACHINE(x) (x == EM_PPC)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#define USE_PLT_ENTRIES
+#define PLT_ENTRY_SIZE 16
+#define USE_PLT_LIST
+#define LIST_ARCHTYPE ElfW(Addr)
+#define USE_LIST
+#define ARCHDATAM       "__ftr_fixup"
+#endif
+
+/* S390 */
+#if defined(__s390__)
+#define MATCH_MACHINE(x) (x == EM_S390)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#define USE_PLT_ENTRIES
+#define PLT_ENTRY_SIZE 8
+#define USE_GOT_ENTRIES
+#define GOT_ENTRY_SIZE 8
+#define USE_SINGLE
+#endif
+
+/* SuperH */
+#if defined(__sh__)
+#define MATCH_MACHINE(x) (x == EM_SH)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#define USE_GOT_ENTRIES
+#define GOT_ENTRY_SIZE 4
+#define USE_SINGLE
+/* the SH changes have only been tested in =little endian= mode */
+/* I'm not sure about big endian, so let's warn: */
+#if defined(__sh__) && BB_BIG_ENDIAN
+# error insmod.c may require changes for use on big endian SH
+#endif
+/* it may or may not work on the SH1/SH2... Error on those also */
+#if ((!(defined(__SH3__) || defined(__SH4__) || defined(__SH5__)))) && (defined(__sh__))
+#error insmod.c may require changes for SH1 or SH2 use
+#endif
+#endif
+
+/* Sparc */
+#if defined(__sparc__)
+#define MATCH_MACHINE(x) (x == EM_SPARC)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#endif
+
+/* v850e */
+#if defined(__v850e__)
+#define MATCH_MACHINE(x) ((x) == EM_V850 || (x) == EM_CYGNUS_V850)
+#define SHT_RELM       SHT_RELA
+#define Elf32_RelM     Elf32_Rela
+#define ELFCLASSM      ELFCLASS32
+#define USE_PLT_ENTRIES
+#define PLT_ENTRY_SIZE 8
+#define USE_SINGLE
+#ifndef EM_CYGNUS_V850 /* grumble */
+#define EM_CYGNUS_V850 0x9080
+#endif
+#define SYMBOL_PREFIX  "_"
+#endif
+
+/* X86_64  */
+#if defined(__x86_64__)
+#define MATCH_MACHINE(x) (x == EM_X86_64)
+#define SHT_RELM       SHT_RELA
+#define USE_GOT_ENTRIES
+#define GOT_ENTRY_SIZE 8
+#define USE_SINGLE
+#define Elf64_RelM     Elf64_Rela
+#define ELFCLASSM      ELFCLASS64
+#endif
+
+#ifndef SHT_RELM
+#error Sorry, but insmod.c does not yet support this architecture...
+#endif
+
+
+//----------------------------------------------------------------------------
+//--------modutils module.h, lines 45-242
+//----------------------------------------------------------------------------
+
+/* Definitions for the Linux module syscall interface.
+   Copyright 1996, 1997 Linux International.
+
+   Contributed by Richard Henderson <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 *);
+
+
+#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);
+
+static struct obj_file *obj_load(FILE *f, int loadprogbits);
+
+static int obj_create_image(struct obj_file *f, char *image);
+
+/* Architecture specific manipulation routines.  */
+
+static struct obj_file *arch_new_file(void);
+
+static struct obj_section *arch_new_section(void);
+
+static struct obj_symbol *arch_new_symbol(void);
+
+static enum obj_reloc arch_apply_relocation(struct obj_file *f,
+                                     struct obj_section *targsec,
+                                     /*struct obj_section *symsec,*/
+                                     struct obj_symbol *sym,
+                                     ElfW(RelM) *rel, ElfW(Addr) value);
+
+static void arch_create_got(struct obj_file *f);
+#if ENABLE_FEATURE_CHECK_TAINTED_MODULE
+static int obj_gpl_license(struct obj_file *f, const char **license);
+#endif /* FEATURE_CHECK_TAINTED_MODULE */
+#endif /* obj.h */
+//----------------------------------------------------------------------------
+//--------end of modutils obj.h
+//----------------------------------------------------------------------------
+
+
+/* SPFX is always a string, so it can be concatenated to string constants.  */
+#ifdef SYMBOL_PREFIX
+#define SPFX   SYMBOL_PREFIX
+#else
+#define SPFX   ""
+#endif
+
+
+#define _PATH_MODULES  "/lib/modules"
+enum { STRVERSIONLEN = 64 };
+
+/*======================================================================*/
+
+#define OPTION_STR "sLo:fkvqx" USE_FEATURE_INSMOD_LOAD_MAP("m")
+enum {
+       OPT_s = 0x1, // -s /* log to syslog */
+               /* Not supported but kernel needs this for request_module(),
+                  as this calls: modprobe -k -s -- <module>
+                  so silently ignore this flag */
+       OPT_L = 0x2, // -L /* Stub warning */
+               /* Compatibility with modprobe.
+                  In theory, this does locking, but we don't do
+                  that.  So be careful and plan your life around not
+                  loading the same module 50 times concurrently. */
+       OPT_o = 0x4, // -o /* name the output module */
+       OPT_f = 0x8, // -f /* force loading */
+       OPT_k = 0x10, // -k /* module loaded by kerneld, auto-cleanable */
+       OPT_v = 0x20, // -v /* verbose output */
+       OPT_q = 0x40, // -q /* silent */
+       OPT_x = 0x80, // -x /* do not export externs */
+       OPT_m = 0x100, // -m /* print module load map */
+};
+#define flag_force_load (option_mask32 & OPT_f)
+#define flag_autoclean (option_mask32 & OPT_k)
+#define flag_verbose (option_mask32 & OPT_v)
+#define flag_quiet (option_mask32 & OPT_q)
+#define flag_noexport (option_mask32 & OPT_x)
+#if ENABLE_FEATURE_INSMOD_LOAD_MAP
+#define flag_print_load_map (option_mask32 & OPT_m)
+#else
+#define flag_print_load_map 0
+#endif
+
+/*======================================================================*/
+
+#if defined(USE_LIST)
+
+struct arch_list_entry
+{
+       struct arch_list_entry *next;
+       LIST_ARCHTYPE addend;
+       int offset;
+       int inited : 1;
+};
+
+#endif
+
+#if defined(USE_SINGLE)
+
+struct arch_single_entry
+{
+       int offset;
+       int inited : 1;
+       int allocated : 1;
+};
+
+#endif
+
+#if defined(__mips__)
+struct mips_hi16
+{
+       struct mips_hi16 *next;
+       ElfW(Addr) *addr;
+       ElfW(Addr) value;
+};
+#endif
+
+struct arch_file {
+       struct obj_file root;
+#if defined(USE_PLT_ENTRIES)
+       struct obj_section *plt;
+#endif
+#if defined(USE_GOT_ENTRIES)
+       struct obj_section *got;
+#endif
+#if defined(__mips__)
+       struct mips_hi16 *mips_hi16_list;
+#endif
+};
+
+struct arch_symbol {
+       struct obj_symbol root;
+#if defined(USE_PLT_ENTRIES)
+#if defined(USE_PLT_LIST)
+       struct arch_list_entry *pltent;
+#else
+       struct arch_single_entry pltent;
+#endif
+#endif
+#if defined(USE_GOT_ENTRIES)
+       struct arch_single_entry gotent;
+#endif
+};
+
+
+struct external_module {
+       const char *name;
+       ElfW(Addr) addr;
+       int used;
+       size_t nsyms;
+       struct new_module_symbol *syms;
+};
+
+static struct new_module_symbol *ksyms;
+static size_t nksyms;
+
+static struct external_module *ext_modules;
+static int n_ext_modules;
+static int n_ext_modules_used;
+
+static char *m_filename;
+static char *m_fullName;
+
+
+/*======================================================================*/
+
+
+static int check_module_name_match(const char *filename,
+               struct stat *statbuf ATTRIBUTE_UNUSED,
+               void *userdata, int depth ATTRIBUTE_UNUSED)
+{
+       char *fullname = (char *) userdata;
+       char *tmp;
+
+       if (fullname[0] == '\0')
+               return FALSE;
+
+       tmp = bb_get_last_path_component_nostrip(filename);
+       if (strcmp(tmp, fullname) == 0) {
+               /* Stop searching if we find a match */
+               m_filename = xstrdup(filename);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+
+/*======================================================================*/
+
+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)
+{
+       struct arch_file *ifile = (struct arch_file *) f;
+       enum obj_reloc ret = obj_reloc_ok;
+       ElfW(Addr) *loc = (ElfW(Addr) *) (targsec->contents + rel->r_offset);
+       ElfW(Addr) dot = targsec->header.sh_addr + rel->r_offset;
+#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES)
+       struct arch_symbol *isym = (struct arch_symbol *) sym;
+#endif
+#if defined(__arm__) || defined(__i386__) || defined(__mc68000__) || defined(__sh__) || defined(__s390__)
+#if defined(USE_GOT_ENTRIES)
+       ElfW(Addr) got = ifile->got ? ifile->got->header.sh_addr : 0;
+#endif
+#endif
+#if defined(USE_PLT_ENTRIES)
+       ElfW(Addr) plt = ifile->plt ? ifile->plt->header.sh_addr : 0;
+       unsigned long *ip;
+# if defined(USE_PLT_LIST)
+       struct arch_list_entry *pe;
+# else
+       struct arch_single_entry *pe;
+# endif
+#endif
+
+       switch (ELF_R_TYPE(rel->r_info)) {
+
+#if defined(__arm__)
+
+               case R_ARM_NONE:
+                       break;
+
+               case R_ARM_ABS32:
+                       *loc += v;
+                       break;
+
+               case R_ARM_GOT32:
+                       goto bb_use_got;
+
+               case R_ARM_GOTPC:
+                       /* relative reloc, always to _GLOBAL_OFFSET_TABLE_
+                        * (which is .got) similar to branch,
+                        * but is full 32 bits relative */
+
+                       *loc += got - dot;
+                       break;
+
+               case R_ARM_PC24:
+               case R_ARM_PLT32:
+                       goto bb_use_plt;
+
+               case R_ARM_GOTOFF: /* address relative to the got */
+                       *loc += v - got;
+                       break;
+
+#elif defined(__cris__)
+
+               case R_CRIS_NONE:
+                       break;
+
+               case R_CRIS_32:
+                       /* CRIS keeps the relocation value in the r_addend field and
+                        * should not use whats in *loc at all
+                        */
+                       *loc = v;
+                       break;
+
+#elif defined(__H8300H__) || defined(__H8300S__)
+
+               case R_H8_DIR24R8:
+                       loc = (ElfW(Addr) *)((ElfW(Addr))loc - 1);
+                       *loc = (*loc & 0xff000000) | ((*loc & 0xffffff) + v);
+                       break;
+               case R_H8_DIR24A8:
+                       *loc += v;
+                       break;
+               case R_H8_DIR32:
+               case R_H8_DIR32A16:
+                       *loc += v;
+                       break;
+               case R_H8_PCREL16:
+                       v -= dot + 2;
+                       if ((ElfW(Sword))v > 0x7fff ||
+                           (ElfW(Sword))v < -(ElfW(Sword))0x8000)
+                               ret = obj_reloc_overflow;
+                       else
+                               *(unsigned short *)loc = v;
+                       break;
+               case R_H8_PCREL8:
+                       v -= dot + 1;
+                       if ((ElfW(Sword))v > 0x7f ||
+                           (ElfW(Sword))v < -(ElfW(Sword))0x80)
+                               ret = obj_reloc_overflow;
+                       else
+                               *(unsigned char *)loc = v;
+                       break;
+
+#elif defined(__i386__)
+
+               case R_386_NONE:
+                       break;
+
+               case R_386_32:
+                       *loc += v;
+                       break;
+
+               case R_386_PLT32:
+               case R_386_PC32:
+                       *loc += v - dot;
+                       break;
+
+               case R_386_GLOB_DAT:
+               case R_386_JMP_SLOT:
+                       *loc = v;
+                       break;
+
+               case R_386_RELATIVE:
+                       *loc += f->baseaddr;
+                       break;
+
+               case R_386_GOTPC:
+                       *loc += got - dot;
+                       break;
+
+               case R_386_GOT32:
+                       goto bb_use_got;
+
+               case R_386_GOTOFF:
+                       *loc += v - got;
+                       break;
+
+#elif defined(__microblaze__)
+               case R_MICROBLAZE_NONE:
+               case R_MICROBLAZE_64_NONE:
+               case R_MICROBLAZE_32_SYM_OP_SYM:
+               case R_MICROBLAZE_32_PCREL:
+                       break;
+
+               case R_MICROBLAZE_64_PCREL: {
+                       /* dot is the address of the current instruction.
+                        * v is the target symbol address.
+                        * So we need to extract the offset in the code,
+                        * adding v, then subtrating the current address
+                        * of this instruction.
+                        * Ex: "IMM 0xFFFE  bralid 0x0000" = "bralid 0xFFFE0000"
+                        */
+
+                       /* Get split offset stored in code */
+                       unsigned int temp = (loc[0] & 0xFFFF) << 16 |
+                                               (loc[1] & 0xFFFF);
+
+                       /* Adjust relative offset. -4 adjustment required
+                        * because dot points to the IMM insn, but branch
+                        * is computed relative to the branch instruction itself.
+                        */
+                       temp += v - dot - 4;
+
+                       /* Store back into code */
+                       loc[0] = (loc[0] & 0xFFFF0000) | temp >> 16;
+                       loc[1] = (loc[1] & 0xFFFF0000) | (temp & 0xFFFF);
+
+                       break;
+               }
+
+               case R_MICROBLAZE_32:
+                       *loc += v;
+                       break;
+
+               case R_MICROBLAZE_64: {
+                       /* Get split pointer stored in code */
+                       unsigned int temp1 = (loc[0] & 0xFFFF) << 16 |
+                                               (loc[1] & 0xFFFF);
+
+                       /* Add reloc offset */
+                       temp1+=v;
+
+                       /* Store back into code */
+                       loc[0] = (loc[0] & 0xFFFF0000) | temp1 >> 16;
+                       loc[1] = (loc[1] & 0xFFFF0000) | (temp1 & 0xFFFF);
+
+                       break;
+               }
+
+               case R_MICROBLAZE_32_PCREL_LO:
+               case R_MICROBLAZE_32_LO:
+               case R_MICROBLAZE_SRO32:
+               case R_MICROBLAZE_SRW32:
+                       ret = obj_reloc_unhandled;
+                       break;
+
+#elif defined(__mc68000__)
+
+               case R_68K_NONE:
+                       break;
+
+               case R_68K_32:
+                       *loc += v;
+                       break;
+
+               case R_68K_8:
+                       if (v > 0xff) {
+                               ret = obj_reloc_overflow;
+                       }
+                       *(char *)loc = v;
+                       break;
+
+               case R_68K_16:
+                       if (v > 0xffff) {
+                               ret = obj_reloc_overflow;
+                       }
+                       *(short *)loc = v;
+                       break;
+
+               case R_68K_PC8:
+                       v -= dot;
+                       if ((ElfW(Sword))v > 0x7f ||
+                                       (ElfW(Sword))v < -(ElfW(Sword))0x80) {
+                               ret = obj_reloc_overflow;
+                       }
+                       *(char *)loc = v;
+                       break;
+
+               case R_68K_PC16:
+                       v -= dot;
+                       if ((ElfW(Sword))v > 0x7fff ||
+                                       (ElfW(Sword))v < -(ElfW(Sword))0x8000) {
+                               ret = obj_reloc_overflow;
+                       }
+                       *(short *)loc = v;
+                       break;
+
+               case R_68K_PC32:
+                       *(int *)loc = v - dot;
+                       break;
+
+               case R_68K_GLOB_DAT:
+               case R_68K_JMP_SLOT:
+                       *loc = v;
+                       break;
+
+               case R_68K_RELATIVE:
+                       *(int *)loc += f->baseaddr;
+                       break;
+
+               case R_68K_GOT32:
+                       goto bb_use_got;
+
+# ifdef R_68K_GOTOFF
+               case R_68K_GOTOFF:
+                       *loc += v - got;
+                       break;
+# endif
+
+#elif defined(__mips__)
+
+               case R_MIPS_NONE:
+                       break;
+
+               case R_MIPS_32:
+                       *loc += v;
+                       break;
+
+               case R_MIPS_26:
+                       if (v % 4)
+                               ret = obj_reloc_dangerous;
+                       if ((v & 0xf0000000) != ((dot + 4) & 0xf0000000))
+                               ret = obj_reloc_overflow;
+                       *loc =
+                               (*loc & ~0x03ffffff) | ((*loc + (v >> 2)) &
+                                                                               0x03ffffff);
+                       break;
+
+               case R_MIPS_HI16:
+                       {
+                               struct mips_hi16 *n;
+
+                               /* We cannot relocate this one now because we don't know the value
+                                  of the carry we need to add.  Save the information, and let LO16
+                                  do the actual relocation.  */
+                               n = xmalloc(sizeof *n);
+                               n->addr = loc;
+                               n->value = v;
+                               n->next = ifile->mips_hi16_list;
+                               ifile->mips_hi16_list = n;
+                               break;
+                       }
+
+               case R_MIPS_LO16:
+                       {
+                               unsigned long insnlo = *loc;
+                               ElfW(Addr) val, vallo;
+
+                               /* Sign extend the addend we extract from the lo insn.  */
+                               vallo = ((insnlo & 0xffff) ^ 0x8000) - 0x8000;
+
+                               if (ifile->mips_hi16_list != NULL) {
+                                       struct mips_hi16 *l;
+
+                                       l = ifile->mips_hi16_list;
+                                       while (l != NULL) {
+                                               struct mips_hi16 *next;
+                                               unsigned long insn;
+
+                                               /* Do the HI16 relocation.  Note that we actually don't
+                                                  need to know anything about the LO16 itself, except where
+                                                  to find the low 16 bits of the addend needed by the LO16.  */
+                                               insn = *l->addr;
+                                               val =
+                                                       ((insn & 0xffff) << 16) +
+                                                       vallo;
+                                               val += v;
+
+                                               /* Account for the sign extension that will happen in the
+                                                  low bits.  */
+                                               val =
+                                                       ((val >> 16) +
+                                                        ((val & 0x8000) !=
+                                                         0)) & 0xffff;
+
+                                               insn = (insn & ~0xffff) | val;
+                                               *l->addr = insn;
+
+                                               next = l->next;
+                                               free(l);
+                                               l = next;
+                                       }
+
+                                       ifile->mips_hi16_list = NULL;
+                               }
+
+                               /* Ok, we're done with the HI16 relocs.  Now deal with the LO16.  */
+                               val = v + vallo;
+                               insnlo = (insnlo & ~0xffff) | (val & 0xffff);
+                               *loc = insnlo;
+                               break;
+                       }
+
+#elif defined(__nios2__)
+
+               case R_NIOS2_NONE:
+                       break;
+
+               case R_NIOS2_BFD_RELOC_32:
+                       *loc += v;
+                       break;
+
+               case R_NIOS2_BFD_RELOC_16:
+                       if (v > 0xffff) {
+                               ret = obj_reloc_overflow;
+                       }
+                       *(short *)loc = v;
+                       break;
+
+               case R_NIOS2_BFD_RELOC_8:
+                       if (v > 0xff) {
+                               ret = obj_reloc_overflow;
+                       }
+                       *(char *)loc = v;
+                       break;
+
+               case R_NIOS2_S16:
+                       {
+                               Elf32_Addr word;
+
+                               if ((Elf32_Sword)v > 0x7fff ||
+                                   (Elf32_Sword)v < -(Elf32_Sword)0x8000) {
+                                       ret = obj_reloc_overflow;
+                               }
+
+                               word = *loc;
+                               *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) |
+                                      (word & 0x3f);
+                       }
+                       break;
+
+               case R_NIOS2_U16:
+                       {
+                               Elf32_Addr word;
+
+                               if (v > 0xffff) {
+                                       ret = obj_reloc_overflow;
+                               }
+
+                               word = *loc;
+                               *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) |
+                                      (word & 0x3f);
+                       }
+                       break;
+
+               case R_NIOS2_PCREL16:
+                       {
+                               Elf32_Addr word;
+
+                               v -= dot + 4;
+                               if ((Elf32_Sword)v > 0x7fff ||
+                                   (Elf32_Sword)v < -(Elf32_Sword)0x8000) {
+                                       ret = obj_reloc_overflow;
+                               }
+
+                               word = *loc;
+                               *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | (word & 0x3f);
+                       }
+                       break;
+
+               case R_NIOS2_GPREL:
+                       {
+                               Elf32_Addr word, gp;
+                               /* get _gp */
+                               gp = obj_symbol_final_value(f, obj_find_symbol(f, SPFX "_gp"));
+                               v-=gp;
+                               if ((Elf32_Sword)v > 0x7fff ||
+                                               (Elf32_Sword)v < -(Elf32_Sword)0x8000) {
+                                       ret = obj_reloc_overflow;
+                               }
+
+                               word = *loc;
+                               *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | (word & 0x3f);
+                       }
+                       break;
+
+               case R_NIOS2_CALL26:
+                       if (v & 3)
+                               ret = obj_reloc_dangerous;
+                       if ((v >> 28) != (dot >> 28))
+                               ret = obj_reloc_overflow;
+                       *loc = (*loc & 0x3f) | ((v >> 2) << 6);
+                       break;
+
+               case R_NIOS2_IMM5:
+                       {
+                               Elf32_Addr word;
+
+                               if (v > 0x1f) {
+                                       ret = obj_reloc_overflow;
+                               }
+
+                               word = *loc & ~0x7c0;
+                               *loc = word | ((v & 0x1f) << 6);
+                       }
+                       break;
+
+               case R_NIOS2_IMM6:
+                       {
+                               Elf32_Addr word;
+
+                               if (v > 0x3f) {
+                                       ret = obj_reloc_overflow;
+                               }
+
+                               word = *loc & ~0xfc0;
+                               *loc = word | ((v & 0x3f) << 6);
+                       }
+                       break;
+
+               case R_NIOS2_IMM8:
+                       {
+                               Elf32_Addr word;
+
+                               if (v > 0xff) {
+                                       ret = obj_reloc_overflow;
+                               }
+
+                               word = *loc & ~0x3fc0;
+                               *loc = word | ((v & 0xff) << 6);
+                       }
+                       break;
+
+               case R_NIOS2_HI16:
+                       {
+                               Elf32_Addr word;
+
+                               word = *loc;
+                               *loc = ((((word >> 22) << 16) | ((v >>16) & 0xffff)) << 6) |
+                                      (word & 0x3f);
+                       }
+                       break;
+
+               case R_NIOS2_LO16:
+                       {
+                               Elf32_Addr word;
+
+                               word = *loc;
+                               *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) |
+                                      (word & 0x3f);
+                       }
+                       break;
+
+               case R_NIOS2_HIADJ16:
+                       {
+                               Elf32_Addr word1, word2;
+
+                               word1 = *loc;
+                               word2 = ((v >> 16) + ((v >> 15) & 1)) & 0xffff;
+                               *loc = ((((word1 >> 22) << 16) | word2) << 6) |
+                                      (word1 & 0x3f);
+                       }
+                       break;
+
+#elif defined(__powerpc64__)
+               /* PPC64 needs a 2.6 kernel, 2.4 module relocation irrelevant */
+
+#elif defined(__powerpc__)
+
+               case R_PPC_ADDR16_HA:
+                       *(unsigned short *)loc = (v + 0x8000) >> 16;
+                       break;
+
+               case R_PPC_ADDR16_HI:
+                       *(unsigned short *)loc = v >> 16;
+                       break;
+
+               case R_PPC_ADDR16_LO:
+                       *(unsigned short *)loc = v;
+                       break;
+
+               case R_PPC_REL24:
+                       goto bb_use_plt;
+
+               case R_PPC_REL32:
+                       *loc = v - dot;
+                       break;
+
+               case R_PPC_ADDR32:
+                       *loc = v;
+                       break;
+
+#elif defined(__s390__)
+
+               case R_390_32:
+                       *(unsigned int *) loc += v;
+                       break;
+               case R_390_16:
+                       *(unsigned short *) loc += v;
+                       break;
+               case R_390_8:
+                       *(unsigned char *) loc += v;
+                       break;
+
+               case R_390_PC32:
+                       *(unsigned int *) loc += v - dot;
+                       break;
+               case R_390_PC16DBL:
+                       *(unsigned short *) loc += (v - dot) >> 1;
+                       break;
+               case R_390_PC16:
+                       *(unsigned short *) loc += v - dot;
+                       break;
+
+               case R_390_PLT32:
+               case R_390_PLT16DBL:
+                       /* find the plt entry and initialize it.  */
+                       pe = (struct arch_single_entry *) &isym->pltent;
+                       if (pe->inited == 0) {
+                               ip = (unsigned long *)(ifile->plt->contents + pe->offset);
+                               ip[0] = 0x0d105810; /* basr 1,0; lg 1,10(1); br 1 */
+                               ip[1] = 0x100607f1;
+                               if (ELF_R_TYPE(rel->r_info) == R_390_PLT16DBL)
+                                       ip[2] = v - 2;
+                               else
+                                       ip[2] = v;
+                               pe->inited = 1;
+                       }
+
+                       /* Insert relative distance to target.  */
+                       v = plt + pe->offset - dot;
+                       if (ELF_R_TYPE(rel->r_info) == R_390_PLT32)
+                               *(unsigned int *) loc = (unsigned int) v;
+                       else if (ELF_R_TYPE(rel->r_info) == R_390_PLT16DBL)
+                               *(unsigned short *) loc = (unsigned short) ((v + 2) >> 1);
+                       break;
+
+               case R_390_GLOB_DAT:
+               case R_390_JMP_SLOT:
+                       *loc = v;
+                       break;
+
+               case R_390_RELATIVE:
+                       *loc += f->baseaddr;
+                       break;
+
+               case R_390_GOTPC:
+                       *(unsigned long *) loc += got - dot;
+                       break;
+
+               case R_390_GOT12:
+               case R_390_GOT16:
+               case R_390_GOT32:
+                       if (!isym->gotent.inited)
+                       {
+                               isym->gotent.inited = 1;
+                               *(ElfW(Addr) *)(ifile->got->contents + isym->gotent.offset) = v;
+                       }
+                       if (ELF_R_TYPE(rel->r_info) == R_390_GOT12)
+                               *(unsigned short *) loc |= (*(unsigned short *) loc + isym->gotent.offset) & 0xfff;
+                       else if (ELF_R_TYPE(rel->r_info) == R_390_GOT16)
+                               *(unsigned short *) loc += isym->gotent.offset;
+                       else if (ELF_R_TYPE(rel->r_info) == R_390_GOT32)
+                               *(unsigned int *) loc += isym->gotent.offset;
+                       break;
+
+# ifndef R_390_GOTOFF32
+#  define R_390_GOTOFF32 R_390_GOTOFF
+# endif
+               case R_390_GOTOFF32:
+                       *loc += v - got;
+                       break;
+
+#elif defined(__sh__)
+
+               case R_SH_NONE:
+                       break;
+
+               case R_SH_DIR32:
+                       *loc += v;
+                       break;
+
+               case R_SH_REL32:
+                       *loc += v - dot;
+                       break;
+
+               case R_SH_PLT32:
+                       *loc = v - dot;
+                       break;
+
+               case R_SH_GLOB_DAT:
+               case R_SH_JMP_SLOT:
+                       *loc = v;
+                       break;
+
+               case R_SH_RELATIVE:
+                       *loc = f->baseaddr + rel->r_addend;
+                       break;
+
+               case R_SH_GOTPC:
+                       *loc = got - dot + rel->r_addend;
+                       break;
+
+               case R_SH_GOT32:
+                       goto bb_use_got;
+
+               case R_SH_GOTOFF:
+                       *loc = v - got;
+                       break;
+
+# if defined(__SH5__)
+               case R_SH_IMM_MEDLOW16:
+               case R_SH_IMM_LOW16:
+                       {
+                               ElfW(Addr) word;
+
+                               if (ELF_R_TYPE(rel->r_info) == R_SH_IMM_MEDLOW16)
+                                       v >>= 16;
+
+                               /*
+                                *  movi and shori have the format:
+                                *
+                                *  |  op  | imm  | reg | reserved |
+                                *   31..26 25..10 9.. 4 3   ..   0
+                                *
+                                * so we simply mask and or in imm.
+                                */
+                               word = *loc & ~0x3fffc00;
+                               word |= (v & 0xffff) << 10;
+
+                               *loc = word;
+
+                               break;
+                       }
+
+               case R_SH_IMM_MEDLOW16_PCREL:
+               case R_SH_IMM_LOW16_PCREL:
+                       {
+                               ElfW(Addr) word;
+
+                               word = *loc & ~0x3fffc00;
+
+                               v -= dot;
+
+                               if (ELF_R_TYPE(rel->r_info) == R_SH_IMM_MEDLOW16_PCREL)
+                                       v >>= 16;
+
+                               word |= (v & 0xffff) << 10;
+
+                               *loc = word;
+
+                               break;
+                       }
+# endif /* __SH5__ */
+
+#elif defined(__v850e__)
+
+               case R_V850_NONE:
+                       break;
+
+               case R_V850_32:
+                       /* We write two shorts instead of a long because even
+                          32-bit insns only need half-word alignment, but
+                          32-bit data needs to be long-word aligned.  */
+                       v += ((unsigned short *)loc)[0];
+                       v += ((unsigned short *)loc)[1] << 16;
+                       ((unsigned short *)loc)[0] = v & 0xffff;
+                       ((unsigned short *)loc)[1] = (v >> 16) & 0xffff;
+                       break;
+
+               case R_V850_22_PCREL:
+                       goto bb_use_plt;
+
+#elif defined(__x86_64__)
+
+               case R_X86_64_NONE:
+                       break;
+
+               case R_X86_64_64:
+                       *loc += v;
+                       break;
+
+               case R_X86_64_32:
+                       *(unsigned int *) loc += v;
+                       if (v > 0xffffffff)
+                       {
+                               ret = obj_reloc_overflow; /* Kernel module compiled without -mcmodel=kernel. */
+                               /* error("Possibly is module compiled without -mcmodel=kernel!"); */
+                       }
+                       break;
+
+               case R_X86_64_32S:
+                       *(signed int *) loc += v;
+                       break;
+
+               case R_X86_64_16:
+                       *(unsigned short *) loc += v;
+                       break;
+
+               case R_X86_64_8:
+                       *(unsigned char *) loc += v;
+                       break;
+
+               case R_X86_64_PC32:
+                       *(unsigned int *) loc += v - dot;
+                       break;
+
+               case R_X86_64_PC16:
+                       *(unsigned short *) loc += v - dot;
+                       break;
+
+               case R_X86_64_PC8:
+                       *(unsigned char *) loc += v - dot;
+                       break;
+
+               case R_X86_64_GLOB_DAT:
+               case R_X86_64_JUMP_SLOT:
+                       *loc = v;
+                       break;
+
+               case R_X86_64_RELATIVE:
+                       *loc += f->baseaddr;
+                       break;
+
+               case R_X86_64_GOT32:
+               case R_X86_64_GOTPCREL:
+                       goto bb_use_got;
+# if 0
+                       if (!isym->gotent.reloc_done)
+                       {
+                               isym->gotent.reloc_done = 1;
+                               *(Elf64_Addr *)(ifile->got->contents + isym->gotent.offset) = v;
+                       }
+                       /* XXX are these really correct?  */
+                       if (ELF64_R_TYPE(rel->r_info) == R_X86_64_GOTPCREL)
+                               *(unsigned int *) loc += v + isym->gotent.offset;
+                       else
+                               *loc += isym->gotent.offset;
+                       break;
+# endif
+
+#else
+# warning "no idea how to handle relocations on your arch"
+#endif
+
+               default:
+                       printf("Warning: unhandled reloc %d\n",(int)ELF_R_TYPE(rel->r_info));
+                       ret = obj_reloc_unhandled;
+                       break;
+
+#if defined(USE_PLT_ENTRIES)
+
+bb_use_plt:
+
+                       /* find the plt entry and initialize it if necessary */
+
+#if defined(USE_PLT_LIST)
+                       for (pe = isym->pltent; pe != NULL && pe->addend != rel->r_addend;)
+                               pe = pe->next;
+#else
+                       pe = &isym->pltent;
+#endif
+
+                       if (! pe->inited) {
+                               ip = (unsigned long *) (ifile->plt->contents + pe->offset);
+
+                               /* generate some machine code */
+
+#if defined(__arm__)
+                               ip[0] = 0xe51ff004;                     /* ldr pc,[pc,#-4] */
+                               ip[1] = v;                              /* sym@ */
+#endif
+#if defined(__powerpc__)
+                               ip[0] = 0x3d600000 + ((v + 0x8000) >> 16);  /* lis r11,sym@ha */
+                               ip[1] = 0x396b0000 + (v & 0xffff);          /* addi r11,r11,sym@l */
+                               ip[2] = 0x7d6903a6;                           /* mtctr r11 */
+                               ip[3] = 0x4e800420;                           /* bctr */
+#endif
+#if defined(__v850e__)
+                               /* We have to trash a register, so we assume that any control
+                                  transfer more than 21-bits away must be a function call
+                                  (so we can use a call-clobbered register).  */
+                               ip[0] = 0x0621 + ((v & 0xffff) << 16);   /* mov sym, r1 ... */
+                               ip[1] = ((v >> 16) & 0xffff) + 0x610000; /* ...; jmp r1 */
+#endif
+                               pe->inited = 1;
+                       }
+
+                       /* relative distance to target */
+                       v -= dot;
+                       /* if the target is too far away.... */
+#if defined(__arm__) || defined(__powerpc__)
+                       if ((int)v < -0x02000000 || (int)v >= 0x02000000)
+#elif defined(__v850e__)
+                               if ((ElfW(Sword))v > 0x1fffff || (ElfW(Sword))v < (ElfW(Sword))-0x200000)
+#endif
+                                       /* go via the plt */
+                                       v = plt + pe->offset - dot;
+
+#if defined(__v850e__)
+                       if (v & 1)
+#else
+                               if (v & 3)
+#endif
+                                       ret = obj_reloc_dangerous;
+
+                       /* merge the offset into the instruction. */
+#if defined(__arm__)
+                       /* Convert to words. */
+                       v >>= 2;
+
+                       *loc = (*loc & ~0x00ffffff) | ((v + *loc) & 0x00ffffff);
+#endif
+#if defined(__powerpc__)
+                       *loc = (*loc & ~0x03fffffc) | (v & 0x03fffffc);
+#endif
+#if defined(__v850e__)
+                       /* We write two shorts instead of a long because even 32-bit insns
+                          only need half-word alignment, but the 32-bit data write needs
+                          to be long-word aligned.  */
+                       ((unsigned short *)loc)[0] =
+                               (*(unsigned short *)loc & 0xffc0) /* opcode + reg */
+                               | ((v >> 16) & 0x3f);             /* offs high part */
+                       ((unsigned short *)loc)[1] =
+                               (v & 0xffff);                    /* offs low part */
+#endif
+                       break;
+#endif /* USE_PLT_ENTRIES */
+
+#if defined(USE_GOT_ENTRIES)
+bb_use_got:
+
+                       /* needs an entry in the .got: set it, once */
+                       if (!isym->gotent.inited) {
+                               isym->gotent.inited = 1;
+                               *(ElfW(Addr) *) (ifile->got->contents + isym->gotent.offset) = v;
+                       }
+                       /* make the reloc with_respect_to_.got */
+#if defined(__sh__)
+                       *loc += isym->gotent.offset + rel->r_addend;
+#elif defined(__i386__) || defined(__arm__) || defined(__mc68000__)
+                       *loc += isym->gotent.offset;
+#endif
+                       break;
+
+#endif /* USE_GOT_ENTRIES */
+       }
+
+       return ret;
+}
+
+
+#if defined(USE_LIST)
+
+static int arch_list_add(ElfW(RelM) *rel, struct arch_list_entry **list,
+                         int offset, int size)
+{
+       struct arch_list_entry *pe;
+
+       for (pe = *list; pe != NULL; pe = pe->next) {
+               if (pe->addend == rel->r_addend) {
+                       break;
+               }
+       }
+
+       if (pe == NULL) {
+               pe = xmalloc(sizeof(struct arch_list_entry));
+               pe->next = *list;
+               pe->addend = rel->r_addend;
+               pe->offset = offset;
+               pe->inited = 0;
+               *list = pe;
+               return size;
+       }
+       return 0;
+}
+
+#endif
+
+#if defined(USE_SINGLE)
+
+static int arch_single_init(/*ElfW(RelM) *rel,*/ struct arch_single_entry *single,
+                            int offset, int size)
+{
+       if (single->allocated == 0) {
+               single->allocated = 1;
+               single->offset = offset;
+               single->inited = 0;
+               return size;
+       }
+       return 0;
+}
+
+#endif
+
+#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES)
+
+static struct obj_section *arch_xsect_init(struct obj_file *f, 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 != -1) {
+               if (symidx >= f->local_symtab_size)
+                       bb_error_msg("local symbol %s with index %ld exceeds local_symtab_size %ld",
+                                       name, (long) symidx, (long) f->local_symtab_size);
+               else
+                       f->local_symtab[symidx] = sym;
+       }
+
+found:
+       sym->name = name;
+       sym->value = value;
+       sym->size = size;
+       sym->secidx = secidx;
+       sym->info = info;
+
+       return sym;
+}
+
+static struct obj_symbol *
+obj_find_symbol(struct obj_file *f, const char *name)
+{
+       struct obj_symbol *sym;
+       unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS;
+
+       for (sym = f->symtab[hash]; sym; sym = sym->next)
+               if (f->symbol_cmp(sym->name, name) == 0)
+                       return sym;
+
+       return NULL;
+}
+
+static ElfW(Addr) obj_symbol_final_value(struct obj_file * f, struct obj_symbol * sym)
+{
+       if (sym) {
+               if (sym->secidx >= SHN_LORESERVE)
+                       return sym->value;
+
+               return sym->value + f->sections[sym->secidx]->header.sh_addr;
+       } else {
+               /* As a special case, a NULL sym has value zero.  */
+               return 0;
+       }
+}
+
+static struct obj_section *obj_find_section(struct obj_file *f, const char *name)
+{
+       int i, n = f->header.e_shnum;
+
+       for (i = 0; i < n; ++i)
+               if (strcmp(f->sections[i]->name, name) == 0)
+                       return f->sections[i];
+
+       return NULL;
+}
+
+static int obj_load_order_prio(struct obj_section *a)
+{
+       unsigned long af, ac;
+
+       af = a->header.sh_flags;
+
+       ac = 0;
+       if (a->name[0] != '.' || strlen(a->name) != 10 ||
+                       strcmp(a->name + 5, ".init"))
+               ac |= 32;
+       if (af & SHF_ALLOC)
+               ac |= 16;
+       if (!(af & SHF_WRITE))
+               ac |= 8;
+       if (af & SHF_EXECINSTR)
+               ac |= 4;
+       if (a->header.sh_type != SHT_NOBITS)
+               ac |= 2;
+
+       return ac;
+}
+
+static void
+obj_insert_section_load_order(struct obj_file *f, struct obj_section *sec)
+{
+       struct obj_section **p;
+       int prio = obj_load_order_prio(sec);
+       for (p = f->load_order_search_start; *p; p = &(*p)->load_next)
+               if (obj_load_order_prio(*p) < prio)
+                       break;
+       sec->load_next = *p;
+       *p = sec;
+}
+
+static struct obj_section *obj_create_alloced_section(struct obj_file *f,
+                               const char *name,
+                               unsigned long align,
+                               unsigned long size)
+{
+       int newidx = f->header.e_shnum++;
+       struct obj_section *sec;
+
+       f->sections = xrealloc(f->sections, (newidx + 1) * sizeof(sec));
+       f->sections[newidx] = sec = arch_new_section();
+
+       sec->header.sh_type = SHT_PROGBITS;
+       sec->header.sh_flags = SHF_WRITE | SHF_ALLOC;
+       sec->header.sh_size = size;
+       sec->header.sh_addralign = align;
+       sec->name = name;
+       sec->idx = newidx;
+       if (size)
+               sec->contents = xmalloc(size);
+
+       obj_insert_section_load_order(f, sec);
+
+       return sec;
+}
+
+static struct obj_section *obj_create_alloced_section_first(struct obj_file *f,
+                               const char *name,
+                               unsigned long align,
+                               unsigned long size)
+{
+       int newidx = f->header.e_shnum++;
+       struct obj_section *sec;
+
+       f->sections = xrealloc(f->sections, (newidx + 1) * sizeof(sec));
+       f->sections[newidx] = sec = arch_new_section();
+
+       sec->header.sh_type = SHT_PROGBITS;
+       sec->header.sh_flags = SHF_WRITE | SHF_ALLOC;
+       sec->header.sh_size = size;
+       sec->header.sh_addralign = align;
+       sec->name = name;
+       sec->idx = newidx;
+       if (size)
+               sec->contents = xmalloc(size);
+
+       sec->load_next = f->load_order;
+       f->load_order = sec;
+       if (f->load_order_search_start == &f->load_order)
+               f->load_order_search_start = &sec->load_next;
+
+       return sec;
+}
+
+static void *obj_extend_section(struct obj_section *sec, unsigned long more)
+{
+       unsigned long oldsize = sec->header.sh_size;
+       if (more) {
+               sec->contents = xrealloc(sec->contents, sec->header.sh_size += more);
+       }
+       return sec->contents + oldsize;
+}
+
+
+/* Conditionally add the symbols from the given symbol set to the
+   new module.  */
+
+static int
+add_symbols_from( struct obj_file *f,
+                                int idx, struct new_module_symbol *syms, size_t nsyms)
+{
+       struct new_module_symbol *s;
+       size_t i;
+       int used = 0;
+#ifdef SYMBOL_PREFIX
+       char *name_buf = 0;
+       size_t name_alloced_size = 0;
+#endif
+#if ENABLE_FEATURE_CHECK_TAINTED_MODULE
+       int gpl;
+
+       gpl = obj_gpl_license(f, NULL) == 0;
+#endif
+       for (i = 0, s = syms; i < nsyms; ++i, ++s) {
+               /* Only add symbols that are already marked external.
+                  If we override locals we may cause problems for
+                  argument initialization.  We will also create a false
+                  dependency on the module.  */
+               struct obj_symbol *sym;
+               char *name;
+
+               /* GPL licensed modules can use symbols exported with
+                * EXPORT_SYMBOL_GPL, so ignore any GPLONLY_ prefix on the
+                * exported names.  Non-GPL modules never see any GPLONLY_
+                * symbols so they cannot fudge it by adding the prefix on
+                * their references.
+                */
+               if (strncmp((char *)s->name, "GPLONLY_", 8) == 0) {
+#if ENABLE_FEATURE_CHECK_TAINTED_MODULE
+                       if (gpl)
+                               s->name += 8;
+                       else
+#endif
+                               continue;
+               }
+               name = (char *)s->name;
+
+#ifdef SYMBOL_PREFIX
+               /* Prepend SYMBOL_PREFIX to the symbol's name (the
+                  kernel exports `C names', but module object files
+                  reference `linker names').  */
+               size_t extra = sizeof SYMBOL_PREFIX;
+               size_t name_size = strlen(name) + extra;
+               if (name_size > name_alloced_size) {
+                       name_alloced_size = name_size * 2;
+                       name_buf = alloca(name_alloced_size);
+               }
+               strcpy(name_buf, SYMBOL_PREFIX);
+               strcpy(name_buf + extra - 1, name);
+               name = name_buf;
+#endif /* SYMBOL_PREFIX */
+
+               sym = obj_find_symbol(f, name);
+               if (sym && !(ELF_ST_BIND(sym->info) == STB_LOCAL)) {
+#ifdef SYMBOL_PREFIX
+                       /* Put NAME_BUF into more permanent storage.  */
+                       name = xmalloc(name_size);
+                       strcpy(name, name_buf);
+#endif
+                       sym = obj_add_symbol(f, name, -1,
+                                       ELF_ST_INFO(STB_GLOBAL,
+                                               STT_NOTYPE),
+                                       idx, s->value, 0);
+                       /* Did our symbol just get installed?  If so, mark the
+                          module as "used".  */
+                       if (sym->secidx == idx)
+                               used = 1;
+               }
+       }
+
+       return used;
+}
+
+static void add_kernel_symbols(struct obj_file *f)
+{
+       struct external_module *m;
+       int i, nused = 0;
+
+       /* Add module symbols first.  */
+
+       for (i = 0, m = ext_modules; i < n_ext_modules; ++i, ++m) {
+               if (m->nsyms
+                && add_symbols_from(f, SHN_HIRESERVE + 2 + i, m->syms, m->nsyms)
+               ) {
+                       m->used = 1;
+                       ++nused;
+               }
+       }
+
+       n_ext_modules_used = nused;
+
+       /* And finally the symbols from the kernel proper.  */
+
+       if (nksyms)
+               add_symbols_from(f, SHN_HIRESERVE + 1, ksyms, nksyms);
+}
+
+static char *get_modinfo_value(struct obj_file *f, const char *key)
+{
+       struct obj_section *sec;
+       char *p, *v, *n, *ep;
+       size_t klen = strlen(key);
+
+       sec = obj_find_section(f, ".modinfo");
+       if (sec == NULL)
+               return NULL;
+       p = sec->contents;
+       ep = p + sec->header.sh_size;
+       while (p < ep) {
+               v = strchr(p, '=');
+               n = strchr(p, '\0');
+               if (v) {
+                       if (p + klen == v && strncmp(p, key, klen) == 0)
+                               return v + 1;
+               } else {
+                       if (p + klen == n && strcmp(p, key) == 0)
+                               return n;
+               }
+               p = n + 1;
+       }
+
+       return NULL;
+}
+
+
+/*======================================================================*/
+/* Functions relating to module loading after 2.1.18.  */
+
+static void
+new_process_module_arguments(struct obj_file *f, int argc, char **argv)
+{
+       while (argc > 0) {
+               char *p, *q, *key, *sym_name;
+               struct obj_symbol *sym;
+               char *contents, *loc;
+               int min, max, n;
+
+               p = *argv;
+               q = strchr(p, '=');
+               if (q == NULL) {
+                       argc--;
+                       continue;
+               }
+
+               key = alloca(q - p + 6);
+               memcpy(key, "parm_", 5);
+               memcpy(key + 5, p, q - p);
+               key[q - p + 5] = 0;
+
+               p = get_modinfo_value(f, key);
+               key += 5;
+               if (p == NULL) {
+                       bb_error_msg_and_die("invalid parameter %s", key);
+               }
+
+#ifdef SYMBOL_PREFIX
+               sym_name = alloca(strlen(key) + sizeof SYMBOL_PREFIX);
+               strcpy(sym_name, SYMBOL_PREFIX);
+               strcat(sym_name, key);
+#else
+               sym_name = key;
+#endif
+               sym = obj_find_symbol(f, sym_name);
+
+               /* Also check that the parameter was not resolved from the kernel.  */
+               if (sym == NULL || sym->secidx > SHN_HIRESERVE) {
+                       bb_error_msg_and_die("symbol for parameter %s not found", key);
+               }
+
+               if (isdigit(*p)) {
+                       min = strtoul(p, &p, 10);
+                       if (*p == '-')
+                               max = strtoul(p + 1, &p, 10);
+                       else
+                               max = min;
+               } else
+                       min = max = 1;
+
+               contents = f->sections[sym->secidx]->contents;
+               loc = contents + sym->value;
+               n = (*++q != '\0');
+
+               while (1) {
+                       if ((*p == 's') || (*p == 'c')) {
+                               char *str;
+
+                               /* Do C quoting if we begin with a ", else slurp the lot.  */
+                               if (*q == '"') {
+                                       char *r;
+
+                                       str = alloca(strlen(q));
+                                       for (r = str, q++; *q != '"'; ++q, ++r) {
+                                               if (*q == '\0')
+                                                       bb_error_msg_and_die("improperly terminated string argument for %s",
+                                                                       key);
+                                               if (*q == '\\')
+                                                       switch (*++q) {
+                                                       case 'a':
+                                                               *r = '\a';
+                                                               break;
+                                                       case 'b':
+                                                               *r = '\b';
+                                                               break;
+                                                       case 'e':
+                                                               *r = '\033';
+                                                               break;
+                                                       case 'f':
+                                                               *r = '\f';
+                                                               break;
+                                                       case 'n':
+                                                               *r = '\n';
+                                                               break;
+                                                       case 'r':
+                                                               *r = '\r';
+                                                               break;
+                                                       case 't':
+                                                               *r = '\t';
+                                                               break;
+
+                                                       case '0':
+                                                       case '1':
+                                                       case '2':
+                                                       case '3':
+                                                       case '4':
+                                                       case '5':
+                                                       case '6':
+                                                       case '7':
+                                                               {
+                                                                       int c = *q - '0';
+                                                                       if (q[1] >= '0' && q[1] <= '7') {
+                                                                               c = (c * 8) + *++q - '0';
+                                                                               if (q[1] >= '0' && q[1] <= '7')
+                                                                                       c = (c * 8) + *++q - '0';
+                                                                       }
+                                                                       *r = c;
+                                                               }
+                                                               break;
+
+                                                       default:
+                                                               *r = *q;
+                                                               break;
+                                                       }
+                                               else
+                                                       *r = *q;
+                                       }
+                                       *r = '\0';
+                                       ++q;
+                               } else {
+                                       char *r;
+
+                                       /* In this case, the string is not quoted. We will break
+                                          it using the coma (like for ints). If the user wants to
+                                          include comas in a string, he just has to quote it */
+
+                                       /* Search the next coma */
+                                       r = strchr(q, ',');
+
+                                       /* Found ? */
+                                       if (r != (char *) NULL) {
+                                               /* Recopy the current field */
+                                               str = alloca(r - q + 1);
+                                               memcpy(str, q, r - q);
+
+                                               /* I don't know if it is useful, as the previous case
+                                                  doesn't nul terminate the string ??? */
+                                               str[r - q] = '\0';
+
+                                               /* Keep next fields */
+                                               q = r;
+                                       } else {
+                                               /* last string */
+                                               str = q;
+                                               q = (char*)"";
+                                       }
+                               }
+
+                               if (*p == 's') {
+                                       /* Normal string */
+                                       obj_string_patch(f, sym->secidx, loc - contents, str);
+                                       loc += tgt_sizeof_char_p;
+                               } else {
+                                       /* Array of chars (in fact, matrix!) */
+                                       unsigned long charssize;        /* size of each member */
+
+                                       /* Get the size of each member */
+                                       /* Probably we should do that outside the loop ? */
+                                       if (!isdigit(*(p + 1))) {
+                                               bb_error_msg_and_die("parameter type 'c' for %s must be followed by"
+                                                               " the maximum size", key);
+                                       }
+                                       charssize = strtoul(p + 1, (char **) NULL, 10);
+
+                                       /* Check length */
+                                       if (strlen(str) >= charssize) {
+                                               bb_error_msg_and_die("string too long for %s (max %ld)", key,
+                                                               charssize - 1);
+                                       }
+
+                                       /* Copy to location */
+                                       strcpy((char *) loc, str);
+                                       loc += charssize;
+                               }
+                       } else {
+                               long v = strtoul(q, &q, 0);
+                               switch (*p) {
+                               case 'b':
+                                       *loc++ = v;
+                                       break;
+                               case 'h':
+                                       *(short *) loc = v;
+                                       loc += tgt_sizeof_short;
+                                       break;
+                               case 'i':
+                                       *(int *) loc = v;
+                                       loc += tgt_sizeof_int;
+                                       break;
+                               case 'l':
+                                       *(long *) loc = v;
+                                       loc += tgt_sizeof_long;
+                                       break;
+
+                               default:
+                                       bb_error_msg_and_die("unknown parameter type '%c' for %s", *p, key);
+                               }
+                       }
+ retry_end_of_value:
+                       switch (*q) {
+                       case '\0':
+                               goto end_of_arg;
+
+                       case ' ':
+                       case '\t':
+                       case '\n':
+                       case '\r':
+                               ++q;
+                               goto retry_end_of_value;
+
+                       case ',':
+                               if (++n > max) {
+                                       bb_error_msg_and_die("too many values for %s (max %d)", key, max);
+                               }
+                               ++q;
+                               break;
+
+                       default:
+                               bb_error_msg_and_die("invalid argument syntax for %s", key);
+                       }
+               }
+ end_of_arg:
+               if (n < min) {
+                       bb_error_msg_and_die("too few values for %s (min %d)", key, min);
+               }
+
+               argc--;
+               argv++;
+       }
+}
+
+#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 = xmalloc(nmod * sizeof(*modules));
+               memset(modules, 0, nmod * sizeof(*modules));
+               for (i = 0, mn = module_names, m = modules;
+                               i < nmod; ++i, ++m, mn += strlen(mn) + 1) {
+                       struct new_module_info info;
+
+                       if (query_module(mn, QM_INFO, &info, sizeof(info), &ret)) {
+                               if (errno == ENOENT) {
+                                       /* The module was removed out from underneath us.  */
+                                       continue;
+                               }
+                               bb_perror_msg_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.  */
+
+       syms = xmalloc(bufsize = 16 * 1024);
+ retry_kern_sym_load:
+       if (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) {
+               if (errno == ENOSPC && bufsize < ret) {
+                       syms = xrealloc(syms, bufsize = ret);
+                       goto retry_kern_sym_load;
+               }
+               bb_perror_msg_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));
+       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.  */
+
+               loaded = alloca(sizeof(int) * (i = f->header.e_shnum));
+               while (--i >= 0)
+                       loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0;
+
+               for (nsyms = i = 0; i < HASH_BUCKETS; ++i) {
+                       struct obj_symbol *sym;
+                       for (sym = f->symtab[i]; sym; sym = sym->next)
+                               if (ELF_ST_BIND(sym->info) != STB_LOCAL
+                                               && sym->secidx <= SHN_HIRESERVE
+                                               && (sym->secidx >= SHN_LORESERVE
+                                                       || loaded[sym->secidx])) {
+                                       ElfW(Addr) ofs = nsyms * 2 * tgt_sizeof_void_p;
+
+                                       obj_symbol_patch(f, sec->idx, ofs, sym);
+                                       obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p,
+                                                       sym->name);
+
+                                       nsyms++;
+                               }
+               }
+
+               obj_extend_section(sec, nsyms * 2 * tgt_sizeof_char_p);
+       }
+
+       return 1;
+}
+
+
+static int
+new_init_module(const char *m_name, struct obj_file *f, unsigned long m_size)
+{
+       struct new_module *module;
+       struct obj_section *sec;
+       void *image;
+       int ret;
+       tgt_long m_addr;
+
+       sec = obj_find_section(f, ".this");
+       if (!sec || !sec->contents) {
+               bb_perror_msg_and_die("corrupt module %s?",m_name);
+       }
+       module = (struct new_module *) sec->contents;
+       m_addr = sec->header.sh_addr;
+
+       module->size_of_struct = sizeof(*module);
+       module->size = m_size;
+       module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0;
+
+       sec = obj_find_section(f, "__ksymtab");
+       if (sec && sec->header.sh_size) {
+               module->syms = sec->header.sh_addr;
+               module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p);
+       }
+
+       if (n_ext_modules_used) {
+               sec = obj_find_section(f, ".kmodtab");
+               module->deps = sec->header.sh_addr;
+               module->ndeps = n_ext_modules_used;
+       }
+
+       module->init =
+               obj_symbol_final_value(f, obj_find_symbol(f, SPFX "init_module"));
+       module->cleanup =
+               obj_symbol_final_value(f, obj_find_symbol(f, SPFX "cleanup_module"));
+
+       sec = obj_find_section(f, "__ex_table");
+       if (sec) {
+               module->ex_table_start = sec->header.sh_addr;
+               module->ex_table_end = sec->header.sh_addr + sec->header.sh_size;
+       }
+
+       sec = obj_find_section(f, ".text.init");
+       if (sec) {
+               module->runsize = sec->header.sh_addr - m_addr;
+       }
+       sec = obj_find_section(f, ".data.init");
+       if (sec) {
+               if (!module->runsize ||
+                               module->runsize > sec->header.sh_addr - m_addr)
+                       module->runsize = sec->header.sh_addr - m_addr;
+       }
+       sec = obj_find_section(f, ARCHDATA_SEC_NAME);
+       if (sec && sec->header.sh_size) {
+               module->archdata_start = (void*)sec->header.sh_addr;
+               module->archdata_end = module->archdata_start + sec->header.sh_size;
+       }
+       sec = obj_find_section(f, KALLSYMS_SEC_NAME);
+       if (sec && sec->header.sh_size) {
+               module->kallsyms_start = (void*)sec->header.sh_addr;
+               module->kallsyms_end = module->kallsyms_start + sec->header.sh_size;
+       }
+
+       /* Whew!  All of the initialization is complete.  Collect the final
+          module image and give it to the kernel.  */
+
+       image = xmalloc(m_size);
+       obj_create_image(f, image);
+
+       ret = init_module(m_name, (struct new_module *) image);
+       if (ret)
+               bb_perror_msg("init_module: %s", m_name);
+
+       free(image);
+
+       return ret == 0;
+}
+
+
+/*======================================================================*/
+
+static 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 = xmalloc(sizeof(*p));
+       p->next = f->string_patches;
+       p->reloc_secidx = secidx;
+       p->reloc_offset = offset;
+       f->string_patches = p;
+
+       strsec = obj_find_section(f, ".kstrtab");
+       if (strsec == NULL) {
+               strsec = obj_create_alloced_section(f, ".kstrtab", 1, len);
+               p->string_offset = 0;
+               loc = strsec->contents;
+       } else {
+               p->string_offset = strsec->header.sh_size;
+               loc = obj_extend_section(strsec, len);
+       }
+       memcpy(loc, string, len);
+}
+
+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;
+                               else if (sym->size < (*p)->sym->size) {
+                                       n = alloca(sizeof(*n));
+                                       n->next = *p;
+                                       n->sym = sym;
+                                       *p = n;
+                                       break;
+                               }
+               }
+       }
+
+       if (common_head) {
+               /* Find the bss section.  */
+               for (i = 0; i < f->header.e_shnum; ++i)
+                       if (f->sections[i]->header.sh_type == SHT_NOBITS)
+                               break;
+
+               /* If for some reason there hadn't been one, create one.  */
+               if (i == f->header.e_shnum) {
+                       struct obj_section *sec;
+
+                       f->sections = xrealloc(f->sections, (i + 1) * sizeof(sec));
+                       f->sections[i] = sec = arch_new_section();
+                       f->header.e_shnum = i + 1;
+
+                       sec->header.sh_type = SHT_PROGBITS;
+                       sec->header.sh_flags = SHF_WRITE | SHF_ALLOC;
+                       sec->name = ".bss";
+                       sec->idx = i;
+               }
+
+               /* Allocate the COMMONS.  */
+               {
+                       ElfW(Addr) bss_size = f->sections[i]->header.sh_size;
+                       ElfW(Addr) max_align = f->sections[i]->header.sh_addralign;
+                       struct common_entry *c;
+
+                       for (c = common_head; c; c = c->next) {
+                               ElfW(Addr) align = c->sym->value;
+
+                               if (align > max_align)
+                                       max_align = align;
+                               if (bss_size & (align - 1))
+                                       bss_size = (bss_size | (align - 1)) + 1;
+
+                               c->sym->secidx = i;
+                               c->sym->value = bss_size;
+
+                               bss_size += c->sym->size;
+                       }
+
+                       f->sections[i]->header.sh_size = bss_size;
+                       f->sections[i]->header.sh_addralign = max_align;
+               }
+       }
+
+       /* For the sake of patch relocation and parameter initialization,
+          allocate zeroed data for NOBITS sections now.  Note that after
+          this we cannot assume NOBITS are really empty.  */
+       for (i = 0; i < f->header.e_shnum; ++i) {
+               struct obj_section *s = f->sections[i];
+               if (s->header.sh_type == SHT_NOBITS) {
+                       if (s->header.sh_size != 0)
+                               s->contents = memset(xmalloc(s->header.sh_size),
+                                               0, s->header.sh_size);
+                       else
+                               s->contents = NULL;
+
+                       s->header.sh_type = SHT_PROGBITS;
+               }
+       }
+}
+
+static unsigned long obj_load_size(struct obj_file *f)
+{
+       unsigned long dot = 0;
+       struct obj_section *sec;
+
+       /* Finalize the positions of the sections relative to one another.  */
+
+       for (sec = f->load_order; sec; sec = sec->load_next) {
+               ElfW(Addr) align;
+
+               align = sec->header.sh_addralign;
+               if (align && (dot & (align - 1)))
+                       dot = (dot | (align - 1)) + 1;
+
+               sec->header.sh_addr = dot;
+               dot += sec->header.sh_size;
+       }
+
+       return dot;
+}
+
+static int obj_relocate(struct obj_file *f, ElfW(Addr) base)
+{
+       int i, n = f->header.e_shnum;
+       int ret = 1;
+
+       /* Finalize the addresses of the sections.  */
+
+       f->baseaddr = base;
+       for (i = 0; i < n; ++i)
+               f->sections[i]->header.sh_addr += base;
+
+       /* And iterate over all of the relocations.  */
+
+       for (i = 0; i < n; ++i) {
+               struct obj_section *relsec, *symsec, *targsec, *strsec;
+               ElfW(RelM) * rel, *relend;
+               ElfW(Sym) * symtab;
+               const char *strtab;
+
+               relsec = f->sections[i];
+               if (relsec->header.sh_type != SHT_RELM)
+                       continue;
+
+               symsec = f->sections[relsec->header.sh_link];
+               targsec = f->sections[relsec->header.sh_info];
+               strsec = f->sections[symsec->header.sh_link];
+
+               rel = (ElfW(RelM) *) relsec->contents;
+               relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM)));
+               symtab = (ElfW(Sym) *) symsec->contents;
+               strtab = (const char *) strsec->contents;
+
+               for (; rel < relend; ++rel) {
+                       ElfW(Addr) value = 0;
+                       struct obj_symbol *intsym = NULL;
+                       unsigned long symndx;
+                       ElfW(Sym) * extsym = 0;
+                       const char *errmsg;
+
+                       /* Attempt to find a value to use for this relocation.  */
+
+                       symndx = ELF_R_SYM(rel->r_info);
+                       if (symndx) {
+                               /* Note we've already checked for undefined symbols.  */
+
+                               extsym = &symtab[symndx];
+                               if (ELF_ST_BIND(extsym->st_info) == STB_LOCAL) {
+                                       /* Local symbols we look up in the local table to be sure
+                                          we get the one that is really intended.  */
+                                       intsym = f->local_symtab[symndx];
+                               } else {
+                                       /* Others we look up in the hash table.  */
+                                       const char *name;
+                                       if (extsym->st_name)
+                                               name = strtab + extsym->st_name;
+                                       else
+                                               name = f->sections[extsym->st_shndx]->name;
+                                       intsym = obj_find_symbol(f, name);
+                               }
+
+                               value = obj_symbol_final_value(f, intsym);
+                               intsym->referenced = 1;
+                       }
+#if SHT_RELM == SHT_RELA
+#if defined(__alpha__) && defined(AXP_BROKEN_GAS)
+                       /* Work around a nasty GAS bug, that is fixed as of 2.7.0.9.  */
+                       if (!extsym || !extsym->st_name ||
+                                       ELF_ST_BIND(extsym->st_info) != STB_LOCAL)
+#endif
+                               value += rel->r_addend;
+#endif
+
+                       /* Do it! */
+                       switch (arch_apply_relocation
+                                       (f, targsec, /*symsec,*/ intsym, rel, value)
+                       ) {
+                       case obj_reloc_ok:
+                               break;
+
+                       case obj_reloc_overflow:
+                               errmsg = "Relocation overflow";
+                               goto bad_reloc;
+                       case obj_reloc_dangerous:
+                               errmsg = "Dangerous relocation";
+                               goto bad_reloc;
+                       case obj_reloc_unhandled:
+                               errmsg = "Unhandled relocation";
+bad_reloc:
+                               if (extsym) {
+                                       bb_error_msg("%s of type %ld for %s", errmsg,
+                                                       (long) ELF_R_TYPE(rel->r_info),
+                                                       strtab + extsym->st_name);
+                               } else {
+                                       bb_error_msg("%s of type %ld", errmsg,
+                                                       (long) ELF_R_TYPE(rel->r_info));
+                               }
+                               ret = 0;
+                               break;
+                       }
+               }
+       }
+
+       /* Finally, take care of the patches.  */
+
+       if (f->string_patches) {
+               struct obj_string_patch *p;
+               struct obj_section *strsec;
+               ElfW(Addr) strsec_base;
+               strsec = obj_find_section(f, ".kstrtab");
+               strsec_base = strsec->header.sh_addr;
+
+               for (p = f->string_patches; p; p = p->next) {
+                       struct obj_section *targsec = f->sections[p->reloc_secidx];
+                       *(ElfW(Addr) *) (targsec->contents + p->reloc_offset)
+                               = strsec_base + p->string_offset;
+               }
+       }
+
+       if (f->symbol_patches) {
+               struct obj_symbol_patch *p;
+
+               for (p = f->symbol_patches; p; p = p->next) {
+                       struct obj_section *targsec = f->sections[p->reloc_secidx];
+                       *(ElfW(Addr) *) (targsec->contents + p->reloc_offset)
+                               = obj_symbol_final_value(f, p->sym);
+               }
+       }
+
+       return ret;
+}
+
+static int obj_create_image(struct obj_file *f, char *image)
+{
+       struct obj_section *sec;
+       ElfW(Addr) base = f->baseaddr;
+
+       for (sec = f->load_order; sec; sec = sec->load_next) {
+               char *secimg;
+
+               if (sec->contents == 0 || sec->header.sh_size == 0)
+                       continue;
+
+               secimg = image + (sec->header.sh_addr - base);
+
+               /* Note that we allocated data for NOBITS sections earlier.  */
+               memcpy(secimg, sec->contents, sec->header.sh_size);
+       }
+
+       return 1;
+}
+
+/*======================================================================*/
+
+static struct obj_file *obj_load(FILE * fp, int loadprogbits ATTRIBUTE_UNUSED)
+{
+       struct obj_file *f;
+       ElfW(Shdr) * section_headers;
+       int 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;
+
+       fseek(fp, 0, SEEK_SET);
+       if (fread(&f->header, sizeof(f->header), 1, fp) != 1) {
+               bb_perror_msg_and_die("error reading ELF header");
+       }
+
+       if (f->header.e_ident[EI_MAG0] != ELFMAG0
+                       || f->header.e_ident[EI_MAG1] != ELFMAG1
+                       || f->header.e_ident[EI_MAG2] != ELFMAG2
+                       || f->header.e_ident[EI_MAG3] != ELFMAG3) {
+               bb_error_msg_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;
+       f->sections = xmalloc(sizeof(struct obj_section *) * shnum);
+       memset(f->sections, 0, sizeof(struct obj_section *) * shnum);
+
+       section_headers = alloca(sizeof(ElfW(Shdr)) * shnum);
+       fseek(fp, f->header.e_shoff, SEEK_SET);
+       if (fread(section_headers, sizeof(ElfW(Shdr)), shnum, fp) != shnum) {
+               bb_perror_msg_and_die("error reading ELF section headers");
+       }
+
+       /* 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:
+                               if (sec->header.sh_size > 0) {
+                                       sec->contents = xmalloc(sec->header.sh_size);
+                                       fseek(fp, sec->header.sh_offset, SEEK_SET);
+                                       if (fread(sec->contents, sec->header.sh_size, 1, fp) != 1) {
+                                               bb_perror_msg_and_die("error reading ELF section data");
+                                       }
+                               } else {
+                                       sec->contents = NULL;
+                               }
+                               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(FILE * fp, struct obj_file* f, char* imagebase)
+{
+       ElfW(Addr) base = f->baseaddr;
+       struct obj_section* sec;
+
+       for (sec = f->load_order; sec; sec = sec->load_next) {
+
+               /* section already loaded? */
+               if (sec->contents != NULL)
+                       continue;
+
+               if (sec->header.sh_size == 0)
+                       continue;
+
+               sec->contents = imagebase + (sec->header.sh_addr - base);
+               fseek(fp, sec->header.sh_offset, SEEK_SET);
+               if (fread(sec->contents, sec->header.sh_size, 1, fp) != 1) {
+                       bb_perror_msg("error reading ELF section data");
+                       return 0;
+               }
+
+       }
+       return 1;
+}
+#endif
+
+static void hide_special_symbols(struct obj_file *f)
+{
+       static const char *const specials[] = {
+               SPFX "cleanup_module",
+               SPFX "init_module",
+               SPFX "kernel_version",
+               NULL
+       };
+
+       struct obj_symbol *sym;
+       const char *const *p;
+
+       for (p = specials; *p; ++p) {
+               sym = obj_find_symbol(f, *p);
+               if (sym != NULL)
+                       sym->info = ELF_ST_INFO(STB_LOCAL, ELF_ST_TYPE(sym->info));
+       }
+}
+
+
+#if ENABLE_FEATURE_CHECK_TAINTED_MODULE
+static int obj_gpl_license(struct obj_file *f, const char **license)
+{
+       struct obj_section *sec;
+       /* This list must match *exactly* the list of allowable licenses in
+        * linux/include/linux/module.h.  Checking for leading "GPL" will not
+        * work, somebody will use "GPL sucks, this is proprietary".
+        */
+       static const char *const gpl_licenses[] = {
+               "GPL",
+               "GPL v2",
+               "GPL and additional rights",
+               "Dual BSD/GPL",
+               "Dual MPL/GPL"
+       };
+
+       sec = obj_find_section(f, ".modinfo");
+       if (sec) {
+               const char *value, *ptr, *endptr;
+               ptr = sec->contents;
+               endptr = ptr + sec->header.sh_size;
+               while (ptr < endptr) {
+                       value = strchr(ptr, '=');
+                       if (value && strncmp(ptr, "license", value-ptr) == 0) {
+                               int i;
+                               if (license)
+                                       *license = value+1;
+                               for (i = 0; i < 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, 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);
+               write(fd, buf, strlen(buf));
+       }
+}
+
+/* Check if loading this module will taint the kernel. */
+static void check_tainted_module(struct obj_file *f, char *m_name)
+{
+       static const char tainted_file[] 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;
+               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;
+               default:
+                       set_tainted(fd, m_name, 1, TAINT_PROPRIETORY_MODULE, "Unexpected return from obj_gpl_license", "");
+                       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 /* FEATURE_CHECK_TAINTED_MODULE */
+
+#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS
+/* add module source, timestamp, kernel version and a symbol for the
+ * start of some sections.  this info is used by ksymoops to do better
+ * debugging.
+ */
+#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  /* FEATURE_INSMOD_VERSION_CHECKING */
+       strncpy(str, "???", sizeof(str));
+       return -1;
+#endif /* FEATURE_INSMOD_VERSION_CHECKING */
+}
+
+/* add module source, timestamp, kernel version and a symbol for the
+ * start of some sections.  this info is used by ksymoops to do better
+ * debugging.
+ */
+static void
+add_ksymoops_symbols(struct obj_file *f, const char *filename,
+                                const char *m_name)
+{
+       static const char symprefix[] 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];
+       int i, l, 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.
+                */
+               l = sizeof(symprefix) +                 /* "__insmod_" */
+                       lm_name +                       /* module name */
+                       2 +                             /* "_O" */
+                       lfilename +                     /* object filename */
+                       2 +                             /* "_M" */
+                       2 * sizeof(statbuf.st_mtime) +  /* mtime in hex */
+                       2 +                             /* "_V" */
+                       8 +                             /* version in dec */
+                       1;                              /* nul */
+               name = xmalloc(l);
+               if (stat(absolute_filename, &statbuf) != 0)
+                       statbuf.st_mtime = 0;
+               version = get_module_version(f, str);   /* -1 if not found */
+               snprintf(name, l, "%s%s_O%s_M%0*lX_V%d",
+                               symprefix, m_name, absolute_filename,
+                               (int)(2 * sizeof(statbuf.st_mtime)), statbuf.st_mtime,
+                               version);
+               sym = obj_add_symbol(f, name, -1,
+                               ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE),
+                               sec->idx, sec->header.sh_addr, 0);
+               if (use_ksymtab)
+                       new_add_ksymtab(f, sym);
+       }
+       free(absolute_filename);
+#ifdef _NOT_SUPPORTED_
+       /* record where the persistent data is going, same address as previous symbol */
+
+       if (f->persist) {
+               l = sizeof(symprefix) +         /* "__insmod_" */
+                       lm_name +               /* module name */
+                       2 +                     /* "_P" */
+                       strlen(f->persist) +    /* data store */
+                       1;                      /* nul */
+               name = xmalloc(l);
+               snprintf(name, l, "%s%s_P%s",
+                               symprefix, m_name, f->persist);
+               sym = obj_add_symbol(f, name, -1, ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE),
+                               sec->idx, sec->header.sh_addr, 0);
+               if (use_ksymtab)
+                       new_add_ksymtab(f, sym);
+       }
+#endif /* _NOT_SUPPORTED_ */
+       /* tag the desired sections if size is non-zero */
+
+       for (i = 0; i < ARRAY_SIZE(section_names); ++i) {
+               sec = obj_find_section(f, section_names[i]);
+               if (sec && sec->header.sh_size) {
+                       l = sizeof(symprefix) +         /* "__insmod_" */
+                               lm_name +               /* module name */
+                               2 +                     /* "_S" */
+                               strlen(sec->name) +     /* section name */
+                               2 +                     /* "_L" */
+                               8 +                     /* length in dec */
+                               1;                      /* nul */
+                       name = xmalloc(l);
+                       snprintf(name, l, "%s%s_S%s_L%ld",
+                                       symprefix, m_name, sec->name,
+                                       (long)sec->header.sh_size);
+                       sym = obj_add_symbol(f, name, -1, ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE),
+                                       sec->idx, sec->header.sh_addr, 0);
+                       if (use_ksymtab)
+                               new_add_ksymtab(f, sym);
+               }
+       }
+}
+#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */
+
+#if ENABLE_FEATURE_INSMOD_LOAD_MAP
+static void print_load_map(struct obj_file *f)
+{
+       struct obj_section *sec;
+#if ENABLE_FEATURE_INSMOD_LOAD_MAP_FULL
+       struct obj_symbol **all, **p;
+       int i, nsyms, *loaded;
+       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 indicies are loaded.  */
+
+       i = f->header.e_shnum;
+       loaded = alloca(sizeof(int) * i);
+       while (--i >= 0)
+               loaded[i] = ((f->sections[i]->header.sh_flags & SHF_ALLOC) != 0);
+
+       /* Collect the symbols we'll be listing.  */
+
+       for (nsyms = i = 0; i < HASH_BUCKETS; ++i)
+               for (sym = f->symtab[i]; sym; sym = sym->next)
+                       if (sym->secidx <= SHN_HIRESERVE
+                                       && (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx]))
+                               ++nsyms;
+
+       all = alloca(nsyms * sizeof(struct obj_symbol *));
+
+       for (i = 0, p = all; i < HASH_BUCKETS; ++i)
+               for (sym = f->symtab[i]; sym; sym = sym->next)
+                       if (sym->secidx <= SHN_HIRESERVE
+                                       && (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx]))
+                               *p++ = sym;
+
+       /* And list them.  */
+       printf("\nSymbols:\n");
+       for (p = all; p < all + nsyms; ++p) {
+               char type = '?';
+               unsigned long value;
+
+               sym = *p;
+               if (sym->secidx == SHN_ABS) {
+                       type = 'A';
+                       value = sym->value;
+               } else if (sym->secidx == SHN_UNDEF) {
+                       type = 'U';
+                       value = 0;
+               } else {
+                       sec = f->sections[sym->secidx];
+
+                       if (sec->header.sh_type == SHT_NOBITS)
+                               type = 'B';
+                       else if (sec->header.sh_flags & SHF_ALLOC) {
+                               if (sec->header.sh_flags & SHF_EXECINSTR)
+                                       type = 'T';
+                               else if (sec->header.sh_flags & SHF_WRITE)
+                                       type = 'D';
+                               else
+                                       type = 'R';
+                       }
+                       value = sym->value + sec->header.sh_addr;
+               }
+
+               if (ELF_ST_BIND(sym->info) == STB_LOCAL)
+                       type = tolower(type);
+
+               printf("%0*lx %c %s\n", (int) (2 * sizeof(void *)), value,
+                               type, sym->name);
+       }
+#endif
+}
+#else /* !FEATURE_INSMOD_LOAD_MAP */
+void print_load_map(struct obj_file *f);
+#endif
+
+int insmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int insmod_main(int argc, char **argv)
+{
+       char *opt_o, *arg1;
+       int len;
+       int k_crcs;
+       char *tmp, *tmp1;
+       unsigned long m_size;
+       ElfW(Addr) m_addr;
+       struct obj_file *f;
+       struct stat st;
+       char *m_name = NULL;
+       int exit_status = EXIT_FAILURE;
+       int m_has_modinfo;
+#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING
+       struct utsname uts_info;
+       char m_strversion[STRVERSIONLEN];
+       int m_version, m_crcs;
+#endif
+#if ENABLE_FEATURE_CLEAN_UP
+       FILE *fp = NULL;
+#else
+       FILE *fp;
+#endif
+       int k_version = 0;
+       struct utsname myuname;
+
+       /* Parse any options */
+       getopt32(argv, OPTION_STR, &opt_o);
+       arg1 = argv[optind];
+       if (option_mask32 & OPT_o) { // -o /* name the output module */
+               free(m_name);
+               m_name = xstrdup(opt_o);
+       }
+
+       if (arg1 == NULL) {
+               bb_show_usage();
+       }
+
+       /* Grab the module name */
+       tmp1 = xstrdup(arg1);
+       tmp = basename(tmp1);
+       len = strlen(tmp);
+
+       if (uname(&myuname) == 0) {
+               if (myuname.release[0] == '2') {
+                       k_version = myuname.release[2] - '0';
+               }
+       }
+
+#if ENABLE_FEATURE_2_6_MODULES
+       if (k_version > 4 && len > 3 && tmp[len - 3] == '.'
+        && tmp[len - 2] == 'k' && tmp[len - 1] == 'o'
+       ) {
+               len -= 3;
+               tmp[len] = '\0';
+       } else
+#endif
+               if (len > 2 && tmp[len - 2] == '.' && tmp[len - 1] == 'o') {
+                       len -= 2;
+                       tmp[len] = '\0';
+               }
+
+
+#if ENABLE_FEATURE_2_6_MODULES
+       if (k_version > 4)
+               m_fullName = xasprintf("%s.ko", tmp);
+       else
+#endif
+               m_fullName = xasprintf("%s.o", tmp);
+
+       if (!m_name) {
+               m_name = tmp;
+       } else {
+               free(tmp1);
+               tmp1 = NULL;       /* flag for free(m_name) before exit() */
+       }
+
+       /* Get a filedesc for the module.  Check that we have a complete path */
+       if (stat(arg1, &st) < 0 || !S_ISREG(st.st_mode)
+        || (fp = fopen(arg1, "r")) == NULL
+       ) {
+               /* Hmm.  Could not open it.  First search under /lib/modules/`uname -r`,
+                * but do not error out yet if we fail to find it... */
+               if (k_version) {        /* uname succeedd */
+                       char *module_dir;
+                       char *tmdn;
+
+                       tmdn = concat_path_file(_PATH_MODULES, myuname.release);
+                       /* Jump through hoops in case /lib/modules/`uname -r`
+                        * is a symlink.  We do not want recursive_action to
+                        * follow symlinks, but we do want to follow the
+                        * /lib/modules/`uname -r` dir, So resolve it ourselves
+                        * if it is a link... */
+                       module_dir = xmalloc_readlink(tmdn);
+                       if (!module_dir)
+                               module_dir = xstrdup(tmdn);
+                       recursive_action(module_dir, ACTION_RECURSE,
+                                       check_module_name_match, NULL, m_fullName, 0);
+                       free(module_dir);
+                       free(tmdn);
+               }
+
+               /* Check if we have found anything yet */
+               if (!m_filename || ((fp = fopen(m_filename, "r")) == NULL)) {
+                       int r;
+                       char *module_dir;
+
+                       free(m_filename);
+                       m_filename = NULL;
+                       module_dir = xmalloc_readlink(_PATH_MODULES);
+                       if (!module_dir)
+                               module_dir = xstrdup(_PATH_MODULES);
+                       /* No module found under /lib/modules/`uname -r`, this
+                        * time cast the net a bit wider.  Search /lib/modules/ */
+                       r = recursive_action(module_dir, ACTION_RECURSE,
+                                       check_module_name_match, NULL, m_fullName, 0);
+                       if (r)
+                               bb_error_msg_and_die("%s: module not found", m_fullName);
+                       free(module_dir);
+                       if (m_filename == NULL
+                        || ((fp = fopen(m_filename, "r")) == NULL)
+                       ) {
+                               bb_error_msg_and_die("%s: module not found", m_fullName);
+                       }
+               }
+       } else
+               m_filename = xstrdup(arg1);
+
+       if (flag_verbose)
+               printf("Using %s\n", m_filename);
+
+#if ENABLE_FEATURE_2_6_MODULES
+       if (k_version > 4) {
+               argv[optind] = m_filename;
+               optind--;
+               return insmod_ng_main(argc - optind, argv + optind);
+       }
+#endif
+
+       f = obj_load(fp, LOADBITS);
+
+       if (get_modinfo_value(f, "kernel_version") == NULL)
+               m_has_modinfo = 0;
+       else
+               m_has_modinfo = 1;
+
+#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING
+       /* Version correspondence?  */
+       if (!flag_quiet) {
+               if (uname(&uts_info) < 0)
+                       uts_info.release[0] = '\0';
+               if (m_has_modinfo) {
+                       m_version = new_get_module_version(f, m_strversion);
+                       if (m_version == -1) {
+                               bb_error_msg_and_die("cannot find the kernel version the module was "
+                                               "compiled for");
+                       }
+               }
+
+               if (strncmp(uts_info.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_filename, m_strversion, uts_info.release);
+                       if (!flag_force_load)
+                               goto out;
+               }
+       }
+       k_crcs = 0;
+#endif /* FEATURE_INSMOD_VERSION_CHECKING */
+
+       if (query_module(NULL, 0, NULL, 0, NULL))
+               bb_error_msg_and_die("not configured to support old kernels");
+       new_get_kernel_symbols();
+       k_crcs = new_is_kernel_checksummed();
+
+#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING
+       m_crcs = 0;
+       if (m_has_modinfo)
+               m_crcs = new_is_module_checksummed(f);
+
+       if (m_crcs != k_crcs)
+               obj_set_symbol_compare(f, ncv_strcmp, ncv_symbol_hash);
+#endif /* FEATURE_INSMOD_VERSION_CHECKING */
+
+       /* Let the module know about the kernel symbols.  */
+       add_kernel_symbols(f);
+
+       /* Allocate common symbols, symbol tables, and string tables.  */
+
+       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 */
+       ++optind;
+       if (optind < argc) {
+               new_process_module_arguments(f, argc - optind, argv + optind);
+       }
+
+       arch_create_got(f);
+       hide_special_symbols(f);
+
+#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS
+       add_ksymoops_symbols(f, m_filename, m_name);
+#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */
+
+       new_create_module_ksymtab(f);
+
+       /* Find current size of the module */
+       m_size = obj_load_size(f);
+
+       m_addr = create_module(m_name, m_size);
+       if (m_addr == -1) switch (errno) {
+               case EEXIST:
+                       bb_error_msg_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(fp, f, (char*)m_addr)) {
+               delete_module(m_name);
+               goto out;
+       }
+#endif
+
+       if (!obj_relocate(f, m_addr)) {
+               delete_module(m_name);
+               goto out;
+       }
+
+       if (!new_init_module(m_name, f, m_size)) {
+               delete_module(m_name);
+               goto out;
+       }
+
+       if (flag_print_load_map)
+               print_load_map(f);
+
+       exit_status = EXIT_SUCCESS;
+
+ out:
+#if ENABLE_FEATURE_CLEAN_UP
+       if (fp)
+               fclose(fp);
+       free(tmp1);
+       if (!tmp1)
+               free(m_name);
+       free(m_filename);
+#endif
+       return exit_status;
+}
+
+#endif /* ENABLE_FEATURE_2_4_MODULES */
+/*
+ * End of big piece of 2.4-specific code
+ */
+
+
+#if ENABLE_FEATURE_2_6_MODULES
+
+#include <sys/mman.h>
+#include <asm/unistd.h>
+#include <sys/syscall.h>
+
+/* We use error numbers in a loose translation... */
+static const char *moderror(int err)
+{
+       switch (err) {
+       case ENOEXEC:
+               return "invalid module format";
+       case ENOENT:
+               return "unknown symbol in module";
+       case ESRCH:
+               return "module has wrong symbol version";
+       case EINVAL:
+               return "invalid parameters";
+       default:
+               return strerror(err);
+       }
+}
+
+#if !ENABLE_FEATURE_2_4_MODULES
+int insmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int insmod_main(int argc ATTRIBUTE_UNUSED, char **argv)
+#else
+static int insmod_ng_main(int argc ATTRIBUTE_UNUSED, char **argv)
+#endif
+{
+       long ret;
+       size_t len;
+       int optlen;
+       void *map;
+       char *filename, *options;
+
+       filename = *++argv;
+       if (!filename)
+               bb_show_usage();
+
+       /* Rest is options */
+       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);
+       }
+
+#if 0
+       /* Any special reason why mmap? It isn't performace critical... */
+       int fd;
+       struct stat st;
+       unsigned long len;
+       fd = xopen(filename, O_RDONLY);
+       fstat(fd, &st);
+       len = st.st_size;
+       map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (map == MAP_FAILED) {
+               bb_perror_msg_and_die("cannot mmap '%s'", filename);
+       }
+
+       /* map == NULL on Blackfin, probably on other MMU-less systems too. Workaround. */
+       if (map == NULL) {
+               map = xmalloc(len);
+               xread(fd, map, len);
+       }
+#else
+       len = MAXINT(ssize_t);
+       map = xmalloc_open_read_close(filename, &len);
+#endif
+
+       ret = syscall(__NR_init_module, map, len, options);
+       if (ret != 0) {
+               bb_error_msg_and_die("cannot insert '%s': %s",
+                               filename, moderror(errno));
+       }
+
+       return 0;
+}
+
+#endif
diff --git a/modutils/lsmod.c b/modutils/lsmod.c
new file mode 100644 (file)
index 0000000..da8663a
--- /dev/null
@@ -0,0 +1,192 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini lsmod implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Modified by Alcove, Julien Gaulmin <julien.gaulmin@alcove.fr> and
+ * Nicolas Ferre <nicolas.ferre@alcove.fr> to support pre 2.1 kernels
+ * (which lack the query_module() interface).
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+
+#if !ENABLE_FEATURE_CHECK_TAINTED_MODULE
+static void check_tainted(void) { bb_putchar('\n'); }
+#else
+#define TAINT_FILENAME                  "/proc/sys/kernel/tainted"
+#define TAINT_PROPRIETORY_MODULE        (1<<0)
+#define TAINT_FORCED_MODULE             (1<<1)
+#define TAINT_UNSAFE_SMP                (1<<2)
+
+static void check_tainted(void)
+{
+       int tainted;
+       FILE *f;
+
+       tainted = 0;
+       f = fopen(TAINT_FILENAME, "r");
+       if (f) {
+               fscanf(f, "%d", &tainted);
+               fclose(f);
+       }
+       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 {
+               printf("    Not tainted\n");
+       }
+}
+#endif
+
+#if ENABLE_FEATURE_QUERY_MODULE_INTERFACE
+
+struct module_info
+{
+       unsigned long addr;
+       unsigned long size;
+       unsigned long flags;
+       long usecount;
+};
+
+
+int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret);
+
+enum {
+/* Values for query_module's which.  */
+       QM_MODULES = 1,
+       QM_DEPS = 2,
+       QM_REFS = 3,
+       QM_SYMBOLS = 4,
+       QM_INFO = 5,
+
+/* Bits of module.flags.  */
+       NEW_MOD_RUNNING = 1,
+       NEW_MOD_DELETED = 2,
+       NEW_MOD_AUTOCLEAN = 4,
+       NEW_MOD_VISITED = 8,
+       NEW_MOD_USED_ONCE = 16,
+       NEW_MOD_INITIALIZING = 64
+};
+
+int lsmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int lsmod_main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       struct module_info info;
+       char *module_names, *mn, *deps, *dn;
+       size_t bufsize, depsize, nmod, count, i, j;
+
+       module_names = deps = NULL;
+       bufsize = depsize = 0;
+       while (query_module(NULL, QM_MODULES, module_names, bufsize, &nmod)) {
+               if (errno != ENOSPC) bb_perror_msg_and_die("QM_MODULES");
+               module_names = xmalloc(bufsize = nmod);
+       }
+
+       deps = xmalloc(depsize = 256);
+       printf("Module\t\t\tSize  Used by");
+       check_tainted();
+
+       for (i = 0, mn = module_names; i < nmod; mn += strlen(mn) + 1, i++) {
+               if (query_module(mn, QM_INFO, &info, sizeof(info), &count)) {
+                       if (errno == ENOENT) {
+                               /* The module was removed out from underneath us. */
+                               continue;
+                       }
+                       /* else choke */
+                       bb_perror_msg_and_die("module %s: QM_INFO", mn);
+               }
+               while (query_module(mn, QM_REFS, deps, depsize, &count)) {
+                       if (errno == ENOENT) {
+                               /* The module was removed out from underneath us. */
+                               continue;
+                       } else if (errno != ENOSPC)
+                               bb_perror_msg_and_die("module %s: QM_REFS", mn);
+                       deps = xrealloc(deps, count);
+               }
+               printf("%-20s%8lu%4ld", mn, info.size, info.usecount);
+               if (info.flags & NEW_MOD_DELETED)
+                       printf(" (deleted)");
+               else if (info.flags & NEW_MOD_INITIALIZING)
+                       printf(" (initializing)");
+               else if (!(info.flags & NEW_MOD_RUNNING))
+                       printf(" (uninitialized)");
+               else {
+                       if (info.flags & NEW_MOD_AUTOCLEAN)
+                               printf(" (autoclean) ");
+                       if (!(info.flags & NEW_MOD_USED_ONCE))
+                               printf(" (unused)");
+               }
+               if (count) printf(" [");
+               for (j = 0, dn = deps; j < count; dn += strlen(dn) + 1, j++) {
+                       printf("%s%s", dn, (j==count-1)? "":" ");
+               }
+               if (count) printf("]");
+
+               bb_putchar('\n');
+       }
+
+#if ENABLE_FEATURE_CLEAN_UP
+       free(module_names);
+#endif
+
+       return 0;
+}
+
+#else /* CONFIG_FEATURE_QUERY_MODULE_INTERFACE */
+
+int lsmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int lsmod_main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       FILE *file = xfopen("/proc/modules", "r");
+
+       printf("Module                  Size  Used by");
+       check_tainted();
+#if ENABLE_FEATURE_LSMOD_PRETTY_2_6_OUTPUT
+       {
+               char *line;
+               while ((line = xmalloc_fgets(file)) != NULL) {
+                       char *tok;
+
+                       tok = strtok(line, " \t");
+                       printf("%-19s", tok);
+                       tok = strtok(NULL, " \t\n");
+                       printf(" %8s", tok);
+                       tok = strtok(NULL, " \t\n");
+                       /* Null if no module unloading support. */
+                       if (tok) {
+                               printf("  %s", tok);
+                               tok = strtok(NULL, "\n");
+                               if (!tok)
+                                       tok = (char*)"";
+                               /* New-style has commas, or -.  If so,
+                               truncate (other fields might follow). */
+                               else if (strchr(tok, ',')) {
+                                       tok = strtok(tok, "\t ");
+                                       /* Strip trailing comma. */
+                                       if (tok[strlen(tok)-1] == ',')
+                                               tok[strlen(tok)-1] = '\0';
+                               } else if (tok[0] == '-'
+                                && (tok[1] == '\0' || isspace(tok[1]))
+                               ) {
+                                       tok = (char*)"";
+                               }
+                               printf(" %s", tok);
+                       }
+                       bb_putchar('\n');
+                       free(line);
+               }
+               fclose(file);
+       }
+#else
+       xprint_and_close_file(file);
+#endif  /*  CONFIG_FEATURE_2_6_MODULES  */
+       return EXIT_SUCCESS;
+}
+
+#endif /* CONFIG_FEATURE_QUERY_MODULE_INTERFACE */
diff --git a/modutils/modprobe.c b/modutils/modprobe.c
new file mode 100644 (file)
index 0000000..f6681a8
--- /dev/null
@@ -0,0 +1,898 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Modprobe written from scratch for BusyBox
+ *
+ * Copyright (c) 2002 by Robert Griebl, griebl@gmx.de
+ * Copyright (c) 2003 by Andrew Dennison, andrew.dennison@motec.com.au
+ * Copyright (c) 2005 by Jim Bauer, jfbauer@nfr.com
+ *
+ * Portions Copyright (c) 2005 by Yann E. MORIN, yann.morin.1998@anciens.enib.fr
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+*/
+
+#include "libbb.h"
+#include <sys/utsname.h>
+#include <fnmatch.h>
+
+#define line_buffer bb_common_bufsiz1
+
+struct mod_opt_t {     /* one-way list of options to pass to a module */
+       char *  m_opt_val;
+       struct mod_opt_t * m_next;
+};
+
+struct dep_t { /* one-way list of dependency rules */
+       /* a dependency rule */
+       char *  m_name;                         /* the module name*/
+       char *  m_path;                         /* the module file path */
+       struct mod_opt_t *  m_options;          /* the module options */
+
+       int     m_isalias  : 1;                 /* the module is an alias */
+       int     m_reserved : 15;                /* stuffin' */
+
+       int     m_depcnt   : 16;                /* the number of dependable module(s) */
+       char ** m_deparr;                       /* the list of dependable module(s) */
+
+       struct dep_t * m_next;                  /* the next dependency rule */
+};
+
+struct mod_list_t {    /* two-way list of modules to process */
+       /* a module description */
+       const char * m_name;
+       char * m_path;
+       struct mod_opt_t * m_options;
+
+       struct mod_list_t * m_prev;
+       struct mod_list_t * m_next;
+};
+
+
+static struct dep_t *depend;
+
+#define MAIN_OPT_STR "acdklnqrst:vVC:"
+#define INSERT_ALL     1        /* a */
+#define DUMP_CONF_EXIT 2        /* c */
+#define D_OPT_IGNORED  4        /* d */
+#define AUTOCLEAN_FLG  8        /* k */
+#define LIST_ALL       16       /* l */
+#define SHOW_ONLY      32       /* n */
+#define QUIET          64       /* q */
+#define REMOVE_OPT     128      /* r */
+#define DO_SYSLOG      256      /* s */
+#define RESTRICT_DIR   512      /* t */
+#define VERBOSE        1024     /* v */
+#define VERSION_ONLY   2048     /* V */
+#define CONFIG_FILE    4096     /* C */
+
+#define autoclean       (option_mask32 & AUTOCLEAN_FLG)
+#define show_only       (option_mask32 & SHOW_ONLY)
+#define quiet           (option_mask32 & QUIET)
+#define remove_opt      (option_mask32 & REMOVE_OPT)
+#define do_syslog       (option_mask32 & DO_SYSLOG)
+#define verbose         (option_mask32 & VERBOSE)
+
+static int parse_tag_value(char *buffer, char **ptag, char **pvalue)
+{
+       char *tag, *value;
+
+       buffer = skip_whitespace(buffer);
+       tag = value = buffer;
+       while (!isspace(*value)) {
+               if (!*value)
+                       return 0;
+               value++;
+       }
+       *value++ = '\0';
+       value = skip_whitespace(value);
+       if (!*value)
+               return 0;
+
+       *ptag = tag;
+       *pvalue = value;
+
+       return 1;
+}
+
+/*
+ * This function appends an option to a list
+ */
+static struct mod_opt_t *append_option(struct mod_opt_t *opt_list, char *opt)
+{
+       struct mod_opt_t *ol = opt_list;
+
+       if (ol) {
+               while (ol->m_next) {
+                       ol = ol->m_next;
+               }
+               ol->m_next = xzalloc(sizeof(struct mod_opt_t));
+               ol = ol->m_next;
+       } else {
+               ol = opt_list = xzalloc(sizeof(struct mod_opt_t));
+       }
+
+       ol->m_opt_val = xstrdup(opt);
+       /*ol->m_next = NULL; - done by xzalloc*/
+
+       return opt_list;
+}
+
+#if ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS
+/* static char* parse_command_string(char* src, char **dst);
+ *   src: pointer to string containing argument
+ *   dst: pointer to where to store the parsed argument
+ *   return value: the pointer to the first char after the parsed argument,
+ *                 NULL if there was no argument parsed (only trailing spaces).
+ *   Note that memory is allocated with xstrdup when a new argument was
+ *   parsed. Don't forget to free it!
+ */
+#define ARG_EMPTY      0x00
+#define ARG_IN_DQUOTES 0x01
+#define ARG_IN_SQUOTES 0x02
+static char *parse_command_string(char *src, char **dst)
+{
+       int opt_status = ARG_EMPTY;
+       char* tmp_str;
+
+       /* Dumb you, I have nothing to do... */
+       if (src == NULL) return src;
+
+       /* Skip leading spaces */
+       while (*src == ' ') {
+               src++;
+       }
+       /* Is the end of string reached? */
+       if (*src == '\0') {
+               return NULL;
+       }
+       /* Reached the start of an argument
+        * By the way, we duplicate a little too much
+        * here but what is too much is freed later. */
+       *dst = tmp_str = xstrdup(src);
+       /* Get to the end of that argument */
+       while (*tmp_str != '\0'
+        && (*tmp_str != ' ' || (opt_status & (ARG_IN_DQUOTES | ARG_IN_SQUOTES)))
+       ) {
+               switch (*tmp_str) {
+               case '\'':
+                       if (opt_status & ARG_IN_DQUOTES) {
+                               /* Already in double quotes, keep current char as is */
+                       } else {
+                               /* shift left 1 char, until end of string: get rid of the opening/closing quotes */
+                               memmove(tmp_str, tmp_str + 1, strlen(tmp_str));
+                               /* mark me: we enter or leave single quotes */
+                               opt_status ^= ARG_IN_SQUOTES;
+                               /* Back one char, as we need to re-scan the new char there. */
+                               tmp_str--;
+                       }
+                       break;
+               case '"':
+                       if (opt_status & ARG_IN_SQUOTES) {
+                               /* Already in single quotes, keep current char as is */
+                       } else {
+                               /* shift left 1 char, until end of string: get rid of the opening/closing quotes */
+                               memmove(tmp_str, tmp_str + 1, strlen(tmp_str));
+                               /* mark me: we enter or leave double quotes */
+                               opt_status ^= ARG_IN_DQUOTES;
+                               /* Back one char, as we need to re-scan the new char there. */
+                               tmp_str--;
+                       }
+                       break;
+               case '\\':
+                       if (opt_status & ARG_IN_SQUOTES) {
+                               /* Between single quotes: keep as is. */
+                       } else {
+                               switch (*(tmp_str+1)) {
+                               case 'a':
+                               case 'b':
+                               case 't':
+                               case 'n':
+                               case 'v':
+                               case 'f':
+                               case 'r':
+                               case '0':
+                                       /* We escaped a special character. For now, keep
+                                        * both the back-slash and the following char. */
+                                       tmp_str++;
+                                       src++;
+                                       break;
+                               default:
+                                       /* We escaped a space or a single or double quote,
+                                        * or a back-slash, or a non-escapable char. Remove
+                                        * the '\' and keep the new current char as is. */
+                                       memmove(tmp_str, tmp_str + 1, strlen(tmp_str));
+                                       break;
+                               }
+                       }
+                       break;
+               /* Any other char that is special shall appear here.
+                * Example: $ starts a variable
+               case '$':
+                       do_variable_expansion();
+                       break;
+                * */
+               default:
+                       /* any other char is kept as is. */
+                       break;
+               }
+               tmp_str++; /* Go to next char */
+               src++; /* Go to next char to find the end of the argument. */
+       }
+       /* End of string, but still no ending quote */
+       if (opt_status & (ARG_IN_DQUOTES | ARG_IN_SQUOTES)) {
+               bb_error_msg_and_die("unterminated (single or double) quote in options list: %s", src);
+       }
+       *tmp_str++ = '\0';
+       *dst = xrealloc(*dst, (tmp_str - *dst));
+       return src;
+}
+#else
+#define parse_command_string(src, dst) (0)
+#endif /* ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS */
+
+/*
+ * This function reads aliases and default module options from a configuration file
+ * (/etc/modprobe.conf syntax). It supports includes (only files, no directories).
+ */
+static void include_conf(struct dep_t **first, struct dep_t **current, char *buffer, int buflen, int fd)
+{
+       int continuation_line = 0;
+
+       // alias parsing is not 100% correct (no correct handling of continuation lines within an alias)!
+
+       while (reads(fd, buffer, buflen)) {
+               int l;
+
+               *strchrnul(buffer, '#') = '\0';
+
+               l = strlen(buffer);
+
+               while (l && isspace(buffer[l-1])) {
+                       buffer[l-1] = '\0';
+                       l--;
+               }
+
+               if (l == 0) {
+                       continuation_line = 0;
+                       continue;
+               }
+
+               if (continuation_line)
+                       continue;
+
+               if ((strncmp(buffer, "alias", 5) == 0) && isspace(buffer[5])) {
+                       char *alias, *mod;
+
+                       if (parse_tag_value(buffer + 6, &alias, &mod)) {
+                               /* handle alias as a module dependent on the aliased module */
+                               if (!*current) {
+                                       (*first) = (*current) = xzalloc(sizeof(struct dep_t));
+                               } else {
+                                       (*current)->m_next = xzalloc(sizeof(struct dep_t));
+                                       (*current) = (*current)->m_next;
+                               }
+                               (*current)->m_name = xstrdup(alias);
+                               (*current)->m_isalias = 1;
+
+                               if ((strcmp(mod, "off") == 0) || (strcmp(mod, "null") == 0)) {
+                                       /*(*current)->m_depcnt = 0; - done by xzalloc */
+                                       /*(*current)->m_deparr = 0;*/
+                               } else {
+                                       (*current)->m_depcnt = 1;
+                                       (*current)->m_deparr = xmalloc(sizeof(char *));
+                                       (*current)->m_deparr[0] = xstrdup(mod);
+                               }
+                               /*(*current)->m_next = NULL; - done by xzalloc */
+                       }
+               } else if ((strncmp(buffer, "options", 7) == 0) && isspace(buffer[7])) {
+                       char *mod, *opt;
+
+                       /* split the line in the module/alias name, and options */
+                       if (parse_tag_value(buffer + 8, &mod, &opt)) {
+                               struct dep_t *dt;
+
+                               /* find the corresponding module */
+                               for (dt = *first; dt; dt = dt->m_next) {
+                                       if (strcmp(dt->m_name, mod) == 0)
+                                               break;
+                               }
+                               if (dt) {
+                                       if (ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS) {
+                                               char* new_opt = NULL;
+                                               while ((opt = parse_command_string(opt, &new_opt))) {
+                                                       dt->m_options = append_option(dt->m_options, new_opt);
+                                               }
+                                       } else {
+                                               dt->m_options = append_option(dt->m_options, opt);
+                                       }
+                               }
+                       }
+               } else if ((strncmp(buffer, "include", 7) == 0) && isspace(buffer[7])) {
+                       int fdi;
+                       char *filename;
+
+                       filename = skip_whitespace(buffer + 8);
+                       fdi = open(filename, O_RDONLY);
+                       if (fdi >= 0) {
+                               include_conf(first, current, buffer, buflen, fdi);
+                               close(fdi);
+                       }
+               }
+       } /* while (reads(...)) */
+}
+
+/*
+ * This function builds a list of dependency rules from /lib/modules/`uname -r`/modules.dep.
+ * It then fills every modules and aliases with their default options, found by parsing
+ * modprobe.conf (or modules.conf, or conf.modules).
+ */
+static struct dep_t *build_dep(void)
+{
+       int fd;
+       struct utsname un;
+       struct dep_t *first = NULL;
+       struct dep_t *current = NULL;
+       char *filename;
+       int continuation_line = 0;
+       int k_version;
+
+       if (uname(&un))
+               bb_error_msg_and_die("can't determine kernel version");
+
+       k_version = 0;
+       if (un.release[0] == '2') {
+               k_version = un.release[2] - '0';
+       }
+
+       filename = xasprintf("/lib/modules/%s/modules.dep", un.release);
+       fd = open(filename, O_RDONLY);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               free(filename);
+       if (fd < 0) {
+               /* Ok, that didn't work.  Fall back to looking in /lib/modules */
+               fd = open("/lib/modules/modules.dep", O_RDONLY);
+               if (fd < 0) {
+                       bb_error_msg_and_die("cannot parse modules.dep");
+               }
+       }
+
+       while (reads(fd, line_buffer, sizeof(line_buffer))) {
+               int l = strlen(line_buffer);
+               char *p = 0;
+
+               while (l > 0 && isspace(line_buffer[l-1])) {
+                       line_buffer[l-1] = '\0';
+                       l--;
+               }
+
+               if (l == 0) {
+                       continuation_line = 0;
+                       continue;
+               }
+
+               /* Is this a new module dep description? */
+               if (!continuation_line) {
+                       /* find the dep beginning */
+                       char *col = strchr(line_buffer, ':');
+                       char *dot = col;
+
+                       if (col) {
+                               /* This line is a dep description */
+                               const char *mods;
+                               char *modpath;
+                               char *mod;
+
+                               /* Find the beginning of the module file name */
+                               *col = '\0';
+                               mods = bb_basename(line_buffer);
+
+                               /* find the path of the module */
+                               modpath = strchr(line_buffer, '/'); /* ... and this is the path */
+                               if (!modpath)
+                                       modpath = line_buffer; /* module with no path */
+                               /* find the end of the module name in the file name */
+                               if (ENABLE_FEATURE_2_6_MODULES &&
+                                   (k_version > 4) && (col[-3] == '.') &&
+                                   (col[-2] == 'k') && (col[-1] == 'o'))
+                                       dot = col - 3;
+                               else if ((col[-2] == '.') && (col[-1] == 'o'))
+                                       dot = col - 2;
+
+                               mod = xstrndup(mods, dot - mods);
+
+                               /* enqueue new module */
+                               if (!current) {
+                                       first = current = xzalloc(sizeof(struct dep_t));
+                               } else {
+                                       current->m_next = xzalloc(sizeof(struct dep_t));
+                                       current = current->m_next;
+                               }
+                               current->m_name = mod;
+                               current->m_path = xstrdup(modpath);
+                               /*current->m_options = NULL; - xzalloc did it*/
+                               /*current->m_isalias = 0;*/
+                               /*current->m_depcnt = 0;*/
+                               /*current->m_deparr = 0;*/
+                               /*current->m_next = 0;*/
+
+                               p = col + 1;
+                       } else
+                               /* this line is not a dep description */
+                               p = NULL;
+               } else
+                       /* It's a dep description continuation */
+                       p = line_buffer;
+
+               while (p && *p && isblank(*p))
+                       p++;
+
+               /* p points to the first dependable module; if NULL, no dependable module */
+               if (p && *p) {
+                       char *end = &line_buffer[l-1];
+                       const char *deps;
+                       char *dep;
+                       char *next;
+                       int ext = 0;
+
+                       while (isblank(*end) || (*end == '\\'))
+                               end--;
+
+                       do {
+                               /* search the end of the dependency */
+                               next = strchr(p, ' ');
+                               if (next) {
+                                       *next = '\0';
+                                       next--;
+                               } else
+                                       next = end;
+
+                               /* find the beginning of the module file name */
+                               deps = bb_basename(p);
+                               if (deps == p) {
+                                       while (isblank(*deps))
+                                               deps++;
+                               }
+
+                               /* find the end of the module name in the file name */
+                               if (ENABLE_FEATURE_2_6_MODULES
+                                && (k_version > 4) && (next[-2] == '.')
+                                && (next[-1] == 'k') && (next[0] == 'o'))
+                                       ext = 3;
+                               else if ((next[-1] == '.') && (next[0] == 'o'))
+                                       ext = 2;
+
+                               /* Cope with blank lines */
+                               if ((next-deps-ext+1) <= 0)
+                                       continue;
+                               dep = xstrndup(deps, next - deps - ext + 1);
+
+                               /* Add the new dependable module name */
+                               current->m_depcnt++;
+                               current->m_deparr = xrealloc(current->m_deparr,
+                                               sizeof(char *) * current->m_depcnt);
+                               current->m_deparr[current->m_depcnt - 1] = dep;
+
+                               p = next + 2;
+                       } while (next < end);
+               }
+
+               /* is there other dependable module(s) ? */
+               continuation_line = (line_buffer[l-1] == '\\');
+       } /* while (reads(...)) */
+       close(fd);
+
+       /*
+        * First parse system-specific options and aliases
+        * as they take precedence over the kernel ones.
+        * >=2.6: we only care about modprobe.conf
+        * <=2.4: we care about modules.conf and conf.modules
+        */
+       if (ENABLE_FEATURE_2_6_MODULES
+        && (fd = open("/etc/modprobe.conf", O_RDONLY)) < 0)
+               if (ENABLE_FEATURE_2_4_MODULES
+                && (fd = open("/etc/modules.conf", O_RDONLY)) < 0)
+                       if (ENABLE_FEATURE_2_4_MODULES)
+                               fd = open("/etc/conf.modules", O_RDONLY);
+
+       if (fd >= 0) {
+               include_conf(&first, &current, line_buffer, sizeof(line_buffer), fd);
+               close(fd);
+       }
+
+       /* Only 2.6 has a modules.alias file */
+       if (ENABLE_FEATURE_2_6_MODULES) {
+               /* Parse kernel-declared module aliases */
+               filename = xasprintf("/lib/modules/%s/modules.alias", un.release);
+               fd = open(filename, O_RDONLY);
+               if (fd < 0) {
+                       /* Ok, that didn't work.  Fall back to looking in /lib/modules */
+                       fd = open("/lib/modules/modules.alias", O_RDONLY);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(filename);
+
+               if (fd >= 0) {
+                       include_conf(&first, &current, line_buffer, sizeof(line_buffer), fd);
+                       close(fd);
+               }
+
+               /* Parse kernel-declared symbol aliases */
+               filename = xasprintf("/lib/modules/%s/modules.symbols", un.release);
+               fd = open(filename, O_RDONLY);
+               if (fd < 0) {
+                       /* Ok, that didn't work.  Fall back to looking in /lib/modules */
+                       fd = open("/lib/modules/modules.symbols", O_RDONLY);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(filename);
+
+               if (fd >= 0) {
+                       include_conf(&first, &current, line_buffer, sizeof(line_buffer), fd);
+                       close(fd);
+               }
+       }
+
+       return first;
+}
+
+/* return 1 = loaded, 0 = not loaded, -1 = can't tell */
+static int already_loaded(const char *name)
+{
+       int fd, ret = 0;
+
+       fd = open("/proc/modules", O_RDONLY);
+       if (fd < 0)
+               return -1;
+
+       while (reads(fd, line_buffer, sizeof(line_buffer))) {
+               char *p;
+
+               p = strchr(line_buffer, ' ');
+               if (p) {
+                       const char *n;
+
+                       // Truncate buffer at first space and check for matches, with
+                       // the idiosyncrasy that _ and - are interchangeable because the
+                       // 2.6 kernel does weird things.
+
+                       *p = '\0';
+                       for (p = line_buffer, n = name; ; p++, n++) {
+                               if (*p != *n) {
+                                       if ((*p == '_' || *p == '-') && (*n == '_' || *n == '-'))
+                                               continue;
+                                       break;
+                               }
+                               // If we made it to the end, that's a match.
+                               if (!*p) {
+                                       ret = 1;
+                                       goto done;
+                               }
+                       }
+               }
+       }
+ done:
+       close(fd);
+       return ret;
+}
+
+static int mod_process(const struct mod_list_t *list, int do_insert)
+{
+       int rc = 0;
+       char **argv = NULL;
+       struct mod_opt_t *opts;
+       int argc_malloc; /* never used when CONFIG_FEATURE_CLEAN_UP not defined */
+       int argc;
+
+       while (list) {
+               argc = 0;
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       argc_malloc = 0;
+               /* If CONFIG_FEATURE_CLEAN_UP is not defined, then we leak memory
+                * each time we allocate memory for argv.
+                * But it is (quite) small amounts of memory that leak each
+                * time a module is loaded,  and it is reclaimed when modprobe
+                * exits anyway (even when standalone shell?).
+                * This could become a problem when loading a module with LOTS of
+                * dependencies, with LOTS of options for each dependencies, with
+                * very little memory on the target... But in that case, the module
+                * would not load because there is no more memory, so there's no
+                * problem. */
+               /* enough for minimal insmod (5 args + NULL) or rmmod (3 args + NULL) */
+               argv = xmalloc(6 * sizeof(char*));
+               if (do_insert) {
+                       if (already_loaded(list->m_name) != 1) {
+                               argv[argc++] = (char*)"insmod";
+                               if (ENABLE_FEATURE_2_4_MODULES) {
+                                       if (do_syslog)
+                                               argv[argc++] = (char*)"-s";
+                                       if (autoclean)
+                                               argv[argc++] = (char*)"-k";
+                                       if (quiet)
+                                               argv[argc++] = (char*)"-q";
+                                       else if (verbose) /* verbose and quiet are mutually exclusive */
+                                               argv[argc++] = (char*)"-v";
+                               }
+                               argv[argc++] = list->m_path;
+                               if (ENABLE_FEATURE_CLEAN_UP)
+                                       argc_malloc = argc;
+                               opts = list->m_options;
+                               while (opts) {
+                                       /* Add one more option */
+                                       argc++;
+                                       argv = xrealloc(argv, (argc + 1) * sizeof(char*));
+                                       argv[argc-1] = opts->m_opt_val;
+                                       opts = opts->m_next;
+                               }
+                       }
+               } else {
+                       /* modutils uses short name for removal */
+                       if (already_loaded(list->m_name) != 0) {
+                               argv[argc++] = (char*)"rmmod";
+                               if (do_syslog)
+                                       argv[argc++] = (char*)"-s";
+                               argv[argc++] = (char*)list->m_name;
+                               if (ENABLE_FEATURE_CLEAN_UP)
+                                       argc_malloc = argc;
+                       }
+               }
+               argv[argc] = NULL;
+
+               if (argc) {
+                       if (verbose) {
+                               printf("%s module %s\n", do_insert?"Loading":"Unloading", list->m_name);
+                       }
+                       if (!show_only) {
+                               int rc2 = wait4pid(spawn(argv));
+
+                               if (do_insert) {
+                                       rc = rc2; /* only last module matters */
+                               } else if (!rc2) {
+                                       rc = 0; /* success if remove any mod */
+                               }
+                       }
+                       if (ENABLE_FEATURE_CLEAN_UP) {
+                               /* the last value in the array has index == argc, but
+                                * it is the terminating NULL, so we must not free it. */
+                               while (argc_malloc < argc) {
+                                       free(argv[argc_malloc++]);
+                               }
+                       }
+               }
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       free(argv);
+                       argv = NULL;
+               }
+               list = do_insert ? list->m_prev : list->m_next;
+       }
+       return (show_only) ? 0 : rc;
+}
+
+/*
+ * Check the matching between a pattern and a module name.
+ * We need this as *_* is equivalent to *-*, even in pattern matching.
+ */
+static int check_pattern(const char* pat_src, const char* mod_src)
+{
+       int ret;
+
+       if (ENABLE_FEATURE_MODPROBE_FANCY_ALIAS) {
+               char* pat;
+               char* mod;
+               char* p;
+
+               pat = xstrdup(pat_src);
+               mod = xstrdup(mod_src);
+
+               for (p = pat; (p = strchr(p, '-')); *p++ = '_');
+               for (p = mod; (p = strchr(p, '-')); *p++ = '_');
+
+               ret = fnmatch(pat, mod, 0);
+
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       free(pat);
+                       free(mod);
+               }
+
+               return ret;
+       }
+       return fnmatch(pat_src, mod_src, 0);
+}
+
+/*
+ * Builds the dependency list (aka stack) of a module.
+ * head: the highest module in the stack (last to insmod, first to rmmod)
+ * tail: the lowest module in the stack (first to insmod, last to rmmod)
+ */
+static void check_dep(char *mod, struct mod_list_t **head, struct mod_list_t **tail)
+{
+       struct mod_list_t *find;
+       struct dep_t *dt;
+       struct mod_opt_t *opt = NULL;
+       char *path = NULL;
+
+       /* Search for the given module name amongst all dependency rules.
+        * The module name in a dependency rule can be a shell pattern,
+        * so try to match the given module name against such a pattern.
+        * Of course if the name in the dependency rule is a plain string,
+        * then we consider it a pattern, and matching will still work. */
+       for (dt = depend; dt; dt = dt->m_next) {
+               if (check_pattern(dt->m_name, mod) == 0) {
+                       break;
+               }
+       }
+
+       if (!dt) {
+               bb_error_msg("module %s not found", mod);
+               return;
+       }
+
+       // resolve alias names
+       while (dt->m_isalias) {
+               if (dt->m_depcnt == 1) {
+                       struct dep_t *adt;
+
+                       for (adt = depend; adt; adt = adt->m_next) {
+                               if (check_pattern(adt->m_name, dt->m_deparr[0]) == 0)
+                                       break;
+                       }
+                       if (adt) {
+                               /* This is the module we are aliased to */
+                               struct mod_opt_t *opts = dt->m_options;
+                               /* Option of the alias are appended to the options of the module */
+                               while (opts) {
+                                       adt->m_options = append_option(adt->m_options, opts->m_opt_val);
+                                       opts = opts->m_next;
+                               }
+                               dt = adt;
+                       } else {
+                               bb_error_msg("module %s not found", mod);
+                               return;
+                       }
+               } else {
+                       bb_error_msg("bad alias %s", dt->m_name);
+                       return;
+               }
+       }
+
+       mod = dt->m_name;
+       path = dt->m_path;
+       opt = dt->m_options;
+
+       // search for duplicates
+       for (find = *head; find; find = find->m_next) {
+               if (strcmp(mod, find->m_name) == 0) {
+                       // found -> dequeue it
+
+                       if (find->m_prev)
+                               find->m_prev->m_next = find->m_next;
+                       else
+                               *head = find->m_next;
+
+                       if (find->m_next)
+                               find->m_next->m_prev = find->m_prev;
+                       else
+                               *tail = find->m_prev;
+
+                       break; // there can be only one duplicate
+               }
+       }
+
+       if (!find) { // did not find a duplicate
+               find = xzalloc(sizeof(struct mod_list_t));
+               find->m_name = mod;
+               find->m_path = path;
+               find->m_options = opt;
+       }
+
+       // enqueue at tail
+       if (*tail)
+               (*tail)->m_next = find;
+       find->m_prev = *tail;
+       find->m_next = NULL; /* possibly NOT done by xzalloc! */
+
+       if (!*head)
+               *head = find;
+       *tail = find;
+
+       if (dt) {
+               int i;
+
+               /* Add all dependable module for that new module */
+               for (i = 0; i < dt->m_depcnt; i++)
+                       check_dep(dt->m_deparr[i], head, tail);
+       }
+}
+
+static int mod_insert(char *mod, int argc, char **argv)
+{
+       struct mod_list_t *tail = NULL;
+       struct mod_list_t *head = NULL;
+       int rc;
+
+       // get dep list for module mod
+       check_dep(mod, &head, &tail);
+
+       rc = 1;
+       if (head && tail) {
+               if (argc) {
+                       int i;
+                       // append module args
+                       for (i = 0; i < argc; i++)
+                               head->m_options = append_option(head->m_options, argv[i]);
+               }
+
+               // process tail ---> head
+               rc = mod_process(tail, 1);
+               if (rc) {
+                       /*
+                        * In case of using udev, multiple instances of modprobe can be
+                        * spawned to load the same module (think of two same usb devices,
+                        * for example; or cold-plugging at boot time). Thus we shouldn't
+                        * fail if the module was loaded, and not by us.
+                        */
+                       if (already_loaded(mod))
+                               rc = 0;
+               }
+       }
+       return rc;
+}
+
+static int mod_remove(char *mod)
+{
+       int rc;
+       static const struct mod_list_t rm_a_dummy = { "-a", NULL, NULL, NULL, NULL };
+
+       struct mod_list_t *head = NULL;
+       struct mod_list_t *tail = NULL;
+
+       if (mod)
+               check_dep(mod, &head, &tail);
+       else  // autoclean
+               head = tail = (struct mod_list_t*) &rm_a_dummy;
+
+       rc = 1;
+       if (head && tail)
+               rc = mod_process(head, 0);  // process head ---> tail
+       return rc;
+}
+
+int modprobe_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int modprobe_main(int argc, char **argv)
+{
+       int rc = EXIT_SUCCESS;
+       char *unused;
+
+       opt_complementary = "q-v:v-q";
+       getopt32(argv, MAIN_OPT_STR, &unused, &unused);
+
+       if (option_mask32 & (DUMP_CONF_EXIT | LIST_ALL))
+               return EXIT_SUCCESS;
+       if (option_mask32 & (RESTRICT_DIR | CONFIG_FILE))
+               bb_error_msg_and_die("-t and -C not supported");
+
+       depend = build_dep();
+
+       if (!depend)
+               bb_error_msg_and_die("cannot parse modules.dep");
+
+       if (remove_opt) {
+               do {
+                       /* argv[optind] can be NULL here */
+                       if (mod_remove(argv[optind])) {
+                               bb_error_msg("failed to remove module %s",
+                                               argv[optind]);
+                               rc = EXIT_FAILURE;
+                       }
+               } while (++optind < argc);
+       } else {
+               if (optind >= argc)
+                       bb_error_msg_and_die("no module or pattern provided");
+
+               if (mod_insert(argv[optind], argc - optind - 1, argv + optind + 1))
+                       bb_error_msg_and_die("failed to load module %s", argv[optind]);
+       }
+
+       /* Here would be a good place to free up memory allocated during the dependencies build. */
+
+       return rc;
+}
diff --git a/modutils/rmmod.c b/modutils/rmmod.c
new file mode 100644 (file)
index 0000000..61cfbd1
--- /dev/null
@@ -0,0 +1,94 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini rmmod 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"
+#include <sys/syscall.h>
+
+#if ENABLE_FEATURE_2_6_MODULES
+static inline void filename2modname(char *modname, const char *afterslash)
+{
+       unsigned int i;
+       int kr_chk = 1;
+
+       if (ENABLE_FEATURE_2_4_MODULES
+                       && get_linux_version_code() <= KERNEL_VERSION(2,6,0))
+                               kr_chk = 0;
+
+       /* Convert to underscores, stop at first . */
+       for (i = 0; afterslash[i] && afterslash[i] != '.'; i++) {
+               if (kr_chk && (afterslash[i] == '-'))
+                       modname[i] = '_';
+               else
+                       modname[i] = afterslash[i];
+       }
+       modname[i] = '\0';
+}
+#else
+void filename2modname(char *modname, const char *afterslash);
+#endif
+
+// There really should be a header file for this...
+
+int query_module(const char *name, int which, void *buf,
+                       size_t bufsize, size_t *ret);
+
+int rmmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int rmmod_main(int argc, char **argv)
+{
+       int n, ret = EXIT_SUCCESS;
+       unsigned int flags = O_NONBLOCK|O_EXCL;
+
+#define misc_buf bb_common_bufsiz1
+
+       /* Parse command line. */
+       n = getopt32(argv, "wfa");
+       if (n & 1)      // --wait
+               flags &= ~O_NONBLOCK;
+       if (n & 2)      // --force
+               flags |= O_TRUNC;
+       if (n & 4) {
+               /* Unload _all_ unused modules via NULL delete_module() call */
+               /* until the number of modules does not change */
+               size_t nmod = 0; /* number of modules */
+               size_t pnmod = -1; /* previous number of modules */
+
+               while (nmod != pnmod) {
+                       if (syscall(__NR_delete_module, NULL, flags) != 0) {
+                               if (errno == EFAULT)
+                                       return ret;
+                               bb_perror_msg_and_die("rmmod");
+                       }
+                       pnmod = nmod;
+                       // the 1 here is QM_MODULES.
+                       if (ENABLE_FEATURE_QUERY_MODULE_INTERFACE && query_module(NULL,
+                                       1, misc_buf, sizeof(misc_buf),
+                                       &nmod))
+                       {
+                               bb_perror_msg_and_die("QM_MODULES");
+                       }
+               }
+               return EXIT_SUCCESS;
+       }
+
+       if (optind == argc)
+               bb_show_usage();
+
+       for (n = optind; n < argc; n++) {
+               if (ENABLE_FEATURE_2_6_MODULES) {
+                       filename2modname(misc_buf, bb_basename(argv[n]));
+               }
+
+               if (syscall(__NR_delete_module, ENABLE_FEATURE_2_6_MODULES ? misc_buf : argv[n], flags)) {
+                       bb_simple_perror_msg(argv[n]);
+                       ret = EXIT_FAILURE;
+               }
+       }
+
+       return ret;
+}
diff --git a/networking/Config.in b/networking/Config.in
new file mode 100644 (file)
index 0000000..f0a9307
--- /dev/null
@@ -0,0 +1,911 @@
+#
+# 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 "Preferentially use 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_SHOW
+#      bool "Support show, showmac and showstp"
+#      default n
+#      depends on BRCTL
+#      help
+#        Add support for option which print the current config:
+#          showmacs, showstp, show
+
+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 DNSD
+       bool "dnsd"
+       default n
+       help
+         Small and static DNS server daemon.
+
+config ETHER_WAKE
+       bool "ether-wake"
+       default n
+       help
+         Send a magic packet to wake up sleeping machines.
+
+config FAKEIDENTD
+       bool "fakeidentd"
+       default n
+       select FEATURE_SYSLOG
+       help
+         fakeidentd listens on the ident port and returns a predefined
+         fake value on any query.
+
+config FTPGET
+       bool "ftpget"
+       default n
+       help
+         Retrieve a remote file via FTP.
+
+config FTPPUT
+       bool "ftpput"
+       default n
+       help
+         Store a remote file via FTP.
+
+config FEATURE_FTPGETPUT_LONG_OPTIONS
+       bool "Enable long options in ftpget/ftpput"
+       default n
+       depends on GETOPT_LONG && (FTPGET || FTPPUT)
+       help
+         Support long options for the ftpget/ftpput applet.
+
+config HOSTNAME
+       bool "hostname"
+       default n
+       help
+         Show or set the system's host name.
+
+config HTTPD
+       bool "httpd"
+       default n
+       help
+         Serve web pages via an HTTP server.
+
+config FEATURE_HTTPD_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_RELOAD_CONFIG_SIGHUP
+       bool "Support reloading of global config file on HUP signal"
+       default n
+       depends on HTTPD
+       help
+         This option enables processing of SIGHUP to reload cached
+         configuration settings.
+
+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_CONFIG_WITH_MIME_TYPES
+       bool "Support loading additional MIME types at run-time"
+       default n
+       depends on HTTPD
+       help
+         This option enables support for additional MIME types at
+         run-time to be specified in the configuration file.
+
+config FEATURE_HTTPD_CGI
+       bool "Support Common Gateway Interface (CGI)"
+       default y
+       depends on HTTPD
+       help
+         This option allows scripts and executables to be invoked
+         when specific URLs are requested.
+
+config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+       bool "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: IF_NAMESIZE = 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 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 SENDMAIL
+       bool "sendmail"
+       default n
+       help
+         Barebones sendmail.
+
+config FETCHMAIL
+       bool "fetchmail"
+       default n
+       help
+         Barebones fetchmail.
+
+config SLATTACH
+       bool "slattach"
+       default n
+       help
+         slattach is a small utility to attach network interfaces to serial lines.
+
+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 DEBUG_TFTP
+       bool "Enable debug"
+       default n
+       depends on TFTP
+       help
+         Enable debug settings for tftp.  This is useful if you're running
+         into problems with tftp as the protocol doesn't help you much when
+         you run into problems.
+
+config TRACEROUTE
+       bool "traceroute"
+       default n
+       help
+         Utility to trace the route of IP packets
+
+config FEATURE_TRACEROUTE_VERBOSE
+       bool "Enable verbose output"
+       default n
+       depends on TRACEROUTE
+       help
+         Add some verbosity to traceroute.  This includes amongst other things
+         hostnames and ICMP response types.
+
+config FEATURE_TRACEROUTE_SOURCE_ROUTE
+       bool "Enable loose source route"
+       default n
+       depends on TRACEROUTE
+       help
+         Add option to specify a loose source route gateway
+         (8 maximum).
+
+config FEATURE_TRACEROUTE_USE_ICMP
+       bool "Use ICMP instead of UDP"
+       default n
+       depends on TRACEROUTE
+       help
+         Add feature to allow for ICMP ECHO instead of UDP datagrams.
+
+source networking/udhcp/Config.in
+
+config VCONFIG
+       bool "vconfig"
+       default n
+       help
+         Creates, removes, and configures VLAN interfaces
+
+config WGET
+       bool "wget"
+       default n
+       help
+         wget is a utility for non-interactive download of files from HTTP,
+         HTTPS, and FTP servers.
+
+config FEATURE_WGET_STATUSBAR
+       bool "Enable a nifty process meter (+2k)"
+       default y
+       depends on WGET
+       help
+         Enable the transfer progress bar for wget transfers.
+
+config FEATURE_WGET_AUTHENTICATION
+       bool "Enable HTTP authentication"
+       default y
+       depends on WGET
+       help
+         Support authenticated HTTP transfers.
+
+config FEATURE_WGET_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 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..be2ef94
--- /dev/null
@@ -0,0 +1,45 @@
+# 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_FETCHMAIL)    += sendmail.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_SENDMAIL)     += sendmail.o
+lib-$(CONFIG_SLATTACH)     += slattach.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_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..c9b9d1d
--- /dev/null
@@ -0,0 +1,491 @@
+/* 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"
+
+#define        ARP_OPT_A (0x1)
+#define        ARP_OPT_p (0x2)
+#define        ARP_OPT_H (0x4)
+#define        ARP_OPT_t (0x8)
+#define        ARP_OPT_i (0x10)
+#define        ARP_OPT_a (0x20)
+#define        ARP_OPT_d (0x40)
+#define        ARP_OPT_n (0x80)        /* do not resolve addresses     */
+#define        ARP_OPT_D (0x100)       /* HW-address is devicename     */
+#define        ARP_OPT_s (0x200)
+#define        ARP_OPT_v (0x400 * DEBUG)       /* debugging output flag        */
+
+
+static const struct aftype *ap; /* current address family       */
+static const struct hwtype *hw; /* current hardware type        */
+static int sockfd;              /* active socket descriptor     */
+static smallint hw_set;         /* flag if hw-type was set (-H) */
+static const char *device = ""; /* current device               */
+
+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((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)
+{
+       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);
+
+       if (arp_flags & ATF_PERM)
+               printf("PERM ");
+       if (arp_flags & ATF_PUBL)
+               printf("PUP ");
+#ifdef HAVE_ATF_MAGIC
+       if (arp_flags & ATF_MAGIC)
+               printf("AUTO ");
+#endif
+#ifdef HAVE_ATF_DONTPUB
+       if (arp_flags & ATF_DONTPUB)
+               printf("DONTPUB ");
+#endif
+       if (arp_flags & ATF_USETRAILERS)
+               printf("TRAIL ");
+
+       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("/proc/net/arp", "r");
+       /* 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       char *hw_type;
+       char *protocol;
+
+       /* Initialize variables... */
+       ap = get_aftype(DFLT_AF);
+       if (!ap)
+               bb_error_msg_and_die("%s: %s not supported", DFLT_AF, "address family");
+
+       getopt32(argv, "A:p:H:t:i:adnDsv", &protocol, &protocol,
+                                &hw_type, &hw_type, &device);
+       argv += optind;
+       if (option_mask32 & ARP_OPT_A || option_mask32 & ARP_OPT_p) {
+               ap = get_aftype(protocol);
+               if (ap == NULL)
+                       bb_error_msg_and_die("%s: unknown %s", protocol, "address family");
+       }
+       if (option_mask32 & ARP_OPT_A || option_mask32 & 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 (option_mask32 & 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");
+       }
+       sockfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+       /* Now see what we have to do here... */
+       if (option_mask32 & (ARP_OPT_d|ARP_OPT_s)) {
+               if (argv[0] == NULL)
+                       bb_error_msg_and_die("need host name");
+               if (option_mask32 & ARP_OPT_s)
+                       return arp_set(argv);
+               return arp_del(argv);
+       }
+       //if (option_mask32 & 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..39dcb7c
--- /dev/null
@@ -0,0 +1,402 @@
+/* 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)
+
+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(ME->sll_hatype);
+       ah->ar_hrd = htons(ARPHRD_ETHER);
+       ah->ar_pro = htons(ETH_P_IP);
+       ah->ar_hln = ME->sll_halen;
+       ah->ar_pln = 4;
+       ah->ar_op = option_mask32 & ADVERT ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST);
+
+       memcpy(p, &ME->sll_addr, ah->ar_hln);
+       p += ME->sll_halen;
+
+       memcpy(p, src_addr, 4);
+       p += 4;
+
+       if (option_mask32 & ADVERT)
+               memcpy(p, &ME->sll_addr, ah->ar_hln);
+       else
+               memcpy(p, &HE->sll_addr, ah->ar_hln);
+       p += ah->ar_hln;
+
+       memcpy(p, dst_addr, 4);
+       p += 4;
+
+       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) ATTRIBUTE_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;
+
+       /* 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 < sizeof(*ah) + 2 * (4 + ah->ar_hln)))
+               return false;
+
+       memcpy(&src_ip, p + ah->ar_hln, 4);
+       memcpy(&dst_ip, p + ah->ar_hln + 4 + ah->ar_hln, 4);
+
+       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 ATTRIBUTE_UNUSED, 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(ifr.ifr_name, device, sizeof(ifr.ifr_name) - 1);
+               /* 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);
+               memcpy(&dst, &lsa->u.sin.sin_addr.s_addr, 4);
+               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);
+
+               if (setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) == -1)
+                       bb_perror_msg("cannot bind to device %s", 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..2bb03dd
--- /dev/null
@@ -0,0 +1,224 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Small implementation of brctl for busybox.
+ *
+ * Copyright (C) 2008 by Bernhard 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>
+
+/* 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
+
+#ifdef ENABLE_FEATURE_BRCTL_SHOW
+#error Remove these
+#endif
+#define ENABLE_FEATURE_BRCTL_SHOW 0
+#define USE_FEATURE_BRCTL_SHOW(...)
+
+#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 ATTRIBUTE_UNUSED, 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;
+#if ENABLE_FEATURE_BRCTL_FANCY
+       unsigned long args[4] = {0, 0, 0, 0};
+       int port;
+       int tmp;
+#endif
+
+       argv++;
+       while (*argv) {
+               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++;
+#if ENABLE_FEATURE_BRCTL_SHOW
+               if (key == ARG_show) { /* show */
+                       goto out; /* FIXME: implement me! :) */
+               }
+#endif
+               fd = xsocket(AF_INET, SOCK_STREAM, 0);
+               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 'show' need at least one argument */
+                       bb_show_usage();
+               safe_strncpy(ifr.ifr_name, br, IFNAMSIZ);
+               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;
+               }
+#if ENABLE_FEATURE_BRCTL_FANCY
+               ifr.ifr_data = (char *) &args;
+               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_stp) < 5) { /* time related ops */
+                       unsigned long op = (key == ARG_setageing) ? BRCTL_SET_AGEING_TIME :
+                                          (key == ARG_setfd) ? BRCTL_SET_BRIDGE_FORWARD_DELAY :
+                                          (key == ARG_sethello) ? BRCTL_SET_BRIDGE_HELLO_TIME :
+                                          /*key == ARG_setmaxage*/ BRCTL_SET_BRIDGE_MAX_AGE;
+                       arm_ioctl(args, op, str_to_jiffies(*argv), 0);
+                       goto fire;
+               }
+               port = -1;
+               if (key == ARG_setpathcost || key == ARG_setportprio) {/* get portnum */
+                       int ifidx[MAX_PORTS];
+                       unsigned i;
+
+                       port = if_nametoindex(*argv);
+                       if (!port)
+                               bb_error_msg_and_die(bb_msg_invalid_arg, *argv, "port");
+                       argv++;
+                       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;
+                               }
+                       }
+               }
+               if (key == ARG_setpathcost
+                || key == ARG_setportprio
+                || key == ARG_setbridgeprio
+               ) {
+                       unsigned long op = (key == ARG_setpathcost) ? BRCTL_SET_PATH_COST :
+                                          (key == ARG_setportprio) ? BRCTL_SET_PORT_PRIORITY :
+                                          /*key == ARG_setbridgeprio*/ BRCTL_SET_BRIDGE_PRIORITY;
+                       unsigned long arg1 = port;
+                       unsigned long arg2;
+# if BRCTL_USE_INTERNAL
+                       tmp = xatoi(*argv);
+# else
+                       if (sscanf(*argv, "%i", &tmp) != 1)
+                               bb_error_msg_and_die(bb_msg_invalid_arg, *argv,
+                                               key == ARG_setpathcost ? "cost" : "prio");
+# endif
+                       if (key == ARG_setbridgeprio) {
+                               arg1 = tmp;
+                               arg2 = 0;
+                       } else
+                               arg2 = tmp;
+                       arm_ioctl(args, op, arg1, arg2);
+               }
+ fire:
+               /* Execute the previously set command.  */
+               xioctl(fd, SIOCDEVPRIVATE, &ifr);
+               argv++;
+#endif
+ done:
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       close(fd);
+       }
+ USE_FEATURE_BRCTL_SHOW(out:)
+       return EXIT_SUCCESS;
+}
diff --git a/networking/dnsd.c b/networking/dnsd.c
new file mode 100644 (file)
index 0000000..cb62d20
--- /dev/null
@@ -0,0 +1,407 @@
+/* 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 {
+       MAX_HOST_LEN = 16,      // longest host name allowed is 15
+       IP_STRING_LEN = 18,     // .xxx.xxx.xxx.xxx\0
+
+//must be strlen('.in-addr.arpa') larger than IP_STRING_LEN
+       MAX_NAME_LEN = (IP_STRING_LEN + 13),
+
+/* Cannot get bigger packets than 512 per RFC1035
+   In practice this can be set considerably smaller:
+   Length of response packet is  header (12B) + 2*type(4B) + 2*class(4B) +
+   ttl(4B) + rlen(2B) + r (MAX_NAME_LEN =21B) +
+   2*querystring (2 MAX_NAME_LEN= 42B), all together 90 Byte
+*/
+       MAX_PACK_LEN = 512,
+
+       DEFAULT_TTL = 30,       // increase this when not testing?
+
+       REQ_A = 1,
+       REQ_PTR = 12
+};
+
+struct dns_head {              // the message from client and first part of response mag
+       uint16_t id;
+       uint16_t flags;
+       uint16_t nquer;         // accepts 0
+       uint16_t nansw;         // 1 in response
+       uint16_t nauth;         // 0
+       uint16_t nadd;          // 0
+};
+struct dns_prop {
+       uint16_t type;
+       uint16_t class;
+};
+struct dns_entry {             // element of known name, ip address and reversed ip address
+       struct dns_entry *next;
+       char ip[IP_STRING_LEN];         // dotted decimal IP
+       char rip[IP_STRING_LEN];        // length decimal reversed IP
+       char name[MAX_HOST_LEN];
+};
+
+static struct dns_entry *dnsentry;
+static uint32_t ttl = DEFAULT_TTL;
+
+static const char *fileconf = "/etc/dnsd.conf";
+
+// Must match getopt32 call
+#define OPT_daemon  (option_mask32 & 0x10)
+#define OPT_verbose (option_mask32 & 0x20)
+
+
+/*
+ * Convert host name from C-string to dns length/string.
+ */
+static void convname(char *a, uint8_t *q)
+{
+       int i = (q[0] == '.') ? 0 : 1;
+       for (; i < MAX_HOST_LEN-1 && *q; i++, q++)
+               a[i] = tolower(*q);
+       a[0] = i - 1;
+       a[i] = 0;
+}
+
+/*
+ * Insert length of substrings instead of dots
+ */
+static void undot(uint8_t * rip)
+{
+       int i = 0, s = 0;
+       while (rip[i])
+               i++;
+       for (--i; i >= 0; i--) {
+               if (rip[i] == '.') {
+                       rip[i] = s;
+                       s = 0;
+               } else s++;
+       }
+}
+
+/*
+ * Read one line of hostname/IP from file
+ * Returns 0 for each valid entry read, -1 at EOF
+ * Assumes all host names are lower case only
+ * Hostnames with more than one label are not handled correctly.
+ * Presently the dot is copied into name without
+ * converting to a length/string substring for that label.
+ */
+static int getfileentry(FILE * fp, struct dns_entry *s)
+{
+       unsigned int a,b,c,d;
+       char *line, *r, *name;
+
+ restart:
+       line = r = xmalloc_fgets(fp);
+       if (!r)
+               return -1;
+       while (*r == ' ' || *r == '\t') {
+               r++;
+               if (!*r || *r == '#' || *r == '\n') {
+                       free(line);
+                       goto restart; /* skipping empty/blank and commented lines  */
+               }
+       }
+       name = r;
+       while (*r != ' ' && *r != '\t')
+               r++;
+       *r++ = '\0';
+       if (sscanf(r, ".%u.%u.%u.%u"+1, &a, &b, &c, &d) != 4) {
+               free(line);
+               goto restart; /* skipping wrong lines */
+       }
+
+       sprintf(s->ip, ".%u.%u.%u.%u"+1, a, b, c, d);
+       sprintf(s->rip, ".%u.%u.%u.%u", d, c, b, a);
+       undot((uint8_t*)s->rip);
+       convname(s->name, (uint8_t*)name);
+
+       if (OPT_verbose)
+               fprintf(stderr, "\tname:%s, ip:%s\n", &(s->name[1]),s->ip);
+
+       free(line);
+       return 0;
+}
+
+/*
+ * Read hostname/IP records from file
+ */
+static void dnsentryinit(void)
+{
+       FILE *fp;
+       struct dns_entry *m, *prev;
+
+       prev = dnsentry = NULL;
+       fp = xfopen(fileconf, "r");
+
+       while (1) {
+               m = xzalloc(sizeof(*m));
+               /*m->next = NULL;*/
+               if (getfileentry(fp, m))
+                       break;
+
+               if (prev == NULL)
+                       dnsentry = m;
+               else
+                       prev->next = m;
+               prev = m;
+       }
+       fclose(fp);
+}
+
+/*
+ * Look query up in dns records and return answer if found
+ * qs is the query string, first byte the string length
+ */
+static int table_lookup(uint16_t type, uint8_t * as, uint8_t * qs)
+{
+       int i;
+       struct dns_entry *d = dnsentry;
+
+       do {
+#if DEBUG
+               char *p,*q;
+               q = (char *)&(qs[1]);
+               p = &(d->name[1]);
+               fprintf(stderr, "\n%s: %d/%d p:%s q:%s %d",
+                       __FUNCTION__, (int)strlen(p), (int)(d->name[0]),
+                       p, q, (int)strlen(q));
+#endif
+               if (type == REQ_A) { /* search by host name */
+                       for (i = 1; i <= (int)(d->name[0]); i++)
+                               if (tolower(qs[i]) != d->name[i])
+                                       break;
+                       if (i > (int)(d->name[0])) {
+                               strcpy((char *)as, d->ip);
+#if DEBUG
+                               fprintf(stderr, " OK as:%s\n", as);
+#endif
+                               return 0;
+                       }
+               } else if (type == REQ_PTR) { /* search by IP-address */
+                       if (!strncmp((char*)&d->rip[1], (char*)&qs[1], strlen(d->rip)-1)) {
+                               strcpy((char *)as, d->name);
+                               return 0;
+                       }
+               }
+               d = d->next;
+       } while (d);
+       return -1;
+}
+
+/*
+ * Decode message and generate answer
+ */
+static int process_packet(uint8_t *buf)
+{
+       uint8_t answstr[MAX_NAME_LEN + 1];
+       struct dns_head *head;
+       struct dns_prop *qprop;
+       uint8_t *from, *answb;
+       uint16_t outr_rlen;
+       uint16_t outr_flags;
+       uint16_t flags;
+       int lookup_result, type, packet_len;
+       int querystr_len;
+
+       answstr[0] = '\0';
+
+       head = (struct dns_head *)buf;
+       if (head->nquer == 0) {
+               bb_error_msg("no queries");
+               return -1;
+       }
+
+       if (head->flags & 0x8000) {
+               bb_error_msg("ignoring response packet");
+               return -1;
+       }
+
+       from = (void *)&head[1];        //  start of query string
+//FIXME: strlen of untrusted data??!
+       querystr_len = strlen((char *)from) + 1 + sizeof(struct dns_prop);
+       answb = from + querystr_len;   // where to append answer block
+
+       outr_rlen = 0;
+       outr_flags = 0;
+
+       qprop = (struct dns_prop *)(answb - 4);
+       type = ntohs(qprop->type);
+
+       // only let REQ_A and REQ_PTR pass
+       if (!(type == REQ_A || type == REQ_PTR)) {
+               goto empty_packet;      /* we can't handle the query type */
+       }
+
+       if (ntohs(qprop->class) != 1 /* class INET */ ) {
+               outr_flags = 4; /* not supported */
+               goto empty_packet;
+       }
+       /* we only support standard queries */
+
+       if ((ntohs(head->flags) & 0x7800) != 0)
+               goto empty_packet;
+
+       // We have a standard query
+       bb_info_msg("%s", (char *)from);
+       lookup_result = table_lookup(type, answstr, from);
+       if (lookup_result != 0) {
+               outr_flags = 3 | 0x0400;        // name do not exist and auth
+               goto empty_packet;
+       }
+       if (type == REQ_A) {    // return an address
+               struct in_addr a; // NB! its "struct { unsigned __long__ s_addr; }"
+               uint32_t v32;
+               if (!inet_aton((char*)answstr, &a)) { //dotted dec to long conv
+                       outr_flags = 1; /* Frmt err */
+                       goto empty_packet;
+               }
+               v32 = a.s_addr; /* in case long != int */
+               memcpy(answstr, &v32, 4);
+               outr_rlen = 4;                  // uint32_t IP
+       } else
+               outr_rlen = strlen((char *)answstr) + 1;        // a host name
+       outr_flags |= 0x0400;                   /* authority-bit */
+       // we have an answer
+       head->nansw = htons(1);
+
+       // copy query block to answer block
+       memcpy(answb, from, querystr_len);
+       answb += querystr_len;
+
+       // and append answer rr
+// FIXME: unaligned accesses??
+       *(uint32_t *) answb = htonl(ttl);
+       answb += 4;
+       *(uint16_t *) answb = htons(outr_rlen);
+       answb += 2;
+       memcpy(answb, answstr, outr_rlen);
+       answb += outr_rlen;
+
+ empty_packet:
+
+       flags = ntohs(head->flags);
+       // clear rcode and RA, set responsebit and our new flags
+       flags |= (outr_flags & 0xff80) | 0x8000;
+       head->flags = htons(flags);
+       head->nauth = head->nadd = 0;
+       head->nquer = htons(1);
+
+       packet_len = answb - buf;
+       return packet_len;
+}
+
+/*
+ * Exit on signal
+ */
+static void interrupt(int sig)
+{
+       /* unlink("/var/run/dnsd.lock"); */
+       bb_error_msg("interrupt, exiting\n");
+       kill_myself_with_sig(sig);
+}
+
+int dnsd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dnsd_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       const char *listen_interface = "0.0.0.0";
+       char *sttl, *sport;
+       len_and_sockaddr *lsa, *from, *to;
+       unsigned lsa_size;
+       int udps;
+       uint16_t port = 53;
+       /* Paranoid sizing: querystring x2 + ttl + outr_rlen + answstr */
+       /* I'd rather see process_packet() fixed instead... */
+       uint8_t buf[MAX_PACK_LEN * 2 + 4 + 2 + (MAX_NAME_LEN+1)];
+
+       getopt32(argv, "i:c:t:p:dv", &listen_interface, &fileconf, &sttl, &sport);
+       //if (option_mask32 & 0x1) // -i
+       //if (option_mask32 & 0x2) // -c
+       if (option_mask32 & 0x4) // -t
+               ttl = xatou_range(sttl, 1, 0xffffffff);
+       if (option_mask32 & 0x8) // -p
+               port = xatou_range(sport, 1, 0xffff);
+
+       if (OPT_verbose) {
+               bb_info_msg("listen_interface: %s", listen_interface);
+               bb_info_msg("ttl: %d, port: %d", ttl, port);
+               bb_info_msg("fileconf: %s", fileconf);
+       }
+
+       if (OPT_daemon) {
+               bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+               openlog(applet_name, LOG_PID, LOG_DAEMON);
+               logmode = LOGMODE_SYSLOG;
+       }
+
+       dnsentryinit();
+
+       signal(SIGINT, interrupt);
+       bb_signals(0
+               /* why? + (1 << SIGPIPE) */
+               + (1 << SIGHUP)
+#ifdef SIGTSTP
+               + (1 << SIGTSTP)
+#endif
+#ifdef SIGURG
+               + (1 << SIGURG)
+#endif
+               , SIG_IGN);
+
+       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);
+
+       bb_info_msg("Accepting UDP packets on %s",
+                       xmalloc_sockaddr2dotted(&lsa->u.sa));
+
+       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("invalid packet size");
+                       continue;
+               }
+               if (OPT_verbose)
+                       bb_info_msg("Got UDP packet");
+               buf[r] = '\0'; /* paranoia */
+               r = process_packet(buf);
+               if (r <= 0)
+                       continue;
+               send_to_from(udps, buf, r, 0, &to->u.sa, &from->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..fcd7dd2
--- /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 ATTRIBUTE_UNUSED, 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(if_hwaddr.ifr_name, ifname, sizeof(if_hwaddr.ifr_name));
+               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(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+               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/ftpgetput.c b/networking/ftpgetput.c
new file mode 100644 (file)
index 0000000..6e2d960
--- /dev/null
@@ -0,0 +1,358 @@
+/* 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 <getopt.h>
+#include "libbb.h"
+
+typedef struct ftp_host_info_s {
+       const char *user;
+       const char *password;
+       struct len_and_sockaddr *lsa;
+} ftp_host_info_t;
+
+static smallint verbose_flag;
+static smallint do_continue;
+
+static void ftp_die(const char *msg, const char *remote) ATTRIBUTE_NORETURN;
+static void ftp_die(const char *msg, const char *remote)
+{
+       /* Guard against garbage from remote server */
+       const char *cp = remote;
+       while (*cp >= ' ' && *cp < '\x7f') cp++;
+       bb_error_msg_and_die("unexpected server response%s%s: %.*s",
+                       msg ? " to " : "", msg ? msg : "",
+                       (int)(cp - remote), remote);
+}
+
+
+static int ftpcmd(const char *s1, const char *s2, FILE *stream, char *buf)
+{
+       unsigned n;
+       if (verbose_flag) {
+               bb_error_msg("cmd %s %s", s1, s2);
+       }
+
+       if (s1) {
+               if (s2) {
+                       fprintf(stream, "%s %s\r\n", s1, s2);
+               } else {
+                       fprintf(stream, "%s\r\n", s1);
+               }
+       }
+       do {
+               char *buf_ptr;
+
+               if (fgets(buf, 510, stream) == NULL) {
+                       bb_perror_msg_and_die("fgets");
+               }
+               buf_ptr = strstr(buf, "\r\n");
+               if (buf_ptr) {
+                       *buf_ptr = '\0';
+               }
+       } while (!isdigit(buf[0]) || buf[3] != ' ');
+
+       buf[3] = '\0';
+       n = xatou(buf);
+       buf[3] = ' ';
+       return n;
+}
+
+static int xconnect_ftpdata(ftp_host_info_t *server, char *buf)
+{
+       char *buf_ptr;
+       unsigned port_num;
+
+       /* 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(server->lsa, htons(port_num));
+       return xconnect_stream(server->lsa);
+}
+
+static FILE *ftp_login(ftp_host_info_t *server)
+{
+       FILE *control_stream;
+       char buf[512];
+
+       /* Connect to the command socket */
+       control_stream = fdopen(xconnect_stream(server->lsa), "r+");
+       if (control_stream == NULL) {
+               /* fdopen failed - extremely unlikely */
+               bb_perror_nomsg_and_die();
+       }
+
+       if (ftpcmd(NULL, NULL, control_stream, buf) != 220) {
+               ftp_die(NULL, buf);
+       }
+
+       /*  Login to the server */
+       switch (ftpcmd("USER", server->user, control_stream, buf)) {
+       case 230:
+               break;
+       case 331:
+               if (ftpcmd("PASS", server->password, control_stream, buf) != 230) {
+                       ftp_die("PASS", buf);
+               }
+               break;
+       default:
+               ftp_die("USER", buf);
+       }
+
+       ftpcmd("TYPE I", NULL, control_stream, buf);
+
+       return control_stream;
+}
+
+#if !ENABLE_FTPGET
+int ftp_receive(ftp_host_info_t *server, FILE *control_stream,
+               const char *local_path, char *server_path);
+#else
+static
+int ftp_receive(ftp_host_info_t *server, FILE *control_stream,
+               const char *local_path, char *server_path)
+{
+       char buf[512];
+/* I think 'filesize' usage here is bogus. Let's see... */
+       //off_t filesize = -1;
+#define filesize ((off_t)-1)
+       int fd_data;
+       int fd_local = -1;
+       off_t beg_range = 0;
+
+       /* Connect to the data socket */
+       if (ftpcmd("PASV", NULL, control_stream, buf) != 227) {
+               ftp_die("PASV", buf);
+       }
+       fd_data = xconnect_ftpdata(server, buf);
+
+       if (ftpcmd("SIZE", server_path, control_stream, buf) == 213) {
+               //filesize = BB_STRTOOFF(buf + 4, NULL, 10);
+               //if (errno || filesize < 0)
+               //      ftp_die("SIZE", buf);
+       } else {
+               do_continue = 0;
+       }
+
+       if (LONE_DASH(local_path)) {
+               fd_local = STDOUT_FILENO;
+               do_continue = 0;
+       }
+
+       if (do_continue) {
+               struct stat sbuf;
+               if (lstat(local_path, &sbuf) < 0) {
+                       bb_perror_msg_and_die("lstat");
+               }
+               if (sbuf.st_size > 0) {
+                       beg_range = sbuf.st_size;
+               } else {
+                       do_continue = 0;
+               }
+       }
+
+       if (do_continue) {
+               sprintf(buf, "REST %"OFF_FMT"d", beg_range);
+               if (ftpcmd(buf, NULL, control_stream, buf) != 350) {
+                       do_continue = 0;
+               } else {
+                       //if (filesize != -1)
+                       //      filesize -= beg_range;
+               }
+       }
+
+       if (ftpcmd("RETR", server_path, control_stream, buf) > 150) {
+               ftp_die("RETR", buf);
+       }
+
+       /* only make a local file if we know that one exists on the remote server */
+       if (fd_local == -1) {
+               if (do_continue) {
+                       fd_local = xopen(local_path, O_APPEND | O_WRONLY);
+               } else {
+                       fd_local = xopen(local_path, O_CREAT | O_TRUNC | O_WRONLY);
+               }
+       }
+
+       /* Copy the file */
+       if (filesize != -1) {
+               if (bb_copyfd_size(fd_data, fd_local, filesize) == -1)
+                       return EXIT_FAILURE;
+       } else {
+               if (bb_copyfd_eof(fd_data, fd_local) == -1)
+                       return EXIT_FAILURE;
+       }
+
+       /* close it all down */
+       close(fd_data);
+       if (ftpcmd(NULL, NULL, control_stream, buf) != 226) {
+               ftp_die(NULL, buf);
+       }
+       ftpcmd("QUIT", NULL, control_stream, buf);
+
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if !ENABLE_FTPPUT
+int ftp_send(ftp_host_info_t *server, FILE *control_stream,
+               const char *server_path, char *local_path);
+#else
+static
+int ftp_send(ftp_host_info_t *server, FILE *control_stream,
+               const char *server_path, char *local_path)
+{
+       struct stat sbuf;
+       char buf[512];
+       int fd_data;
+       int fd_local;
+       int response;
+
+       /*  Connect to the data socket */
+       if (ftpcmd("PASV", NULL, control_stream, buf) != 227) {
+               ftp_die("PASV", buf);
+       }
+       fd_data = xconnect_ftpdata(server, buf);
+
+       /* get the local file */
+       fd_local = STDIN_FILENO;
+       if (NOT_LONE_DASH(local_path)) {
+               fd_local = xopen(local_path, O_RDONLY);
+               fstat(fd_local, &sbuf);
+
+               sprintf(buf, "ALLO %"OFF_FMT"u", sbuf.st_size);
+               response = ftpcmd(buf, NULL, control_stream, buf);
+               switch (response) {
+               case 200:
+               case 202:
+                       break;
+               default:
+                       close(fd_local);
+                       ftp_die("ALLO", buf);
+                       break;
+               }
+       }
+       response = ftpcmd("STOR", server_path, control_stream, buf);
+       switch (response) {
+       case 125:
+       case 150:
+               break;
+       default:
+               close(fd_local);
+               ftp_die("STOR", buf);
+       }
+
+       /* transfer the file  */
+       if (bb_copyfd_eof(fd_local, fd_data) == -1) {
+               exit(EXIT_FAILURE);
+       }
+
+       /* close it all down */
+       close(fd_data);
+       if (ftpcmd(NULL, NULL, control_stream, buf) != 226) {
+               ftp_die("close", buf);
+       }
+       ftpcmd("QUIT", NULL, control_stream, buf);
+
+       return EXIT_SUCCESS;
+}
+#endif
+
+#define FTPGETPUT_OPT_CONTINUE 1
+#define FTPGETPUT_OPT_VERBOSE  2
+#define FTPGETPUT_OPT_USER     4
+#define FTPGETPUT_OPT_PASSWORD 8
+#define FTPGETPUT_OPT_PORT     16
+
+#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
+static const 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       /* content-length of the file */
+       unsigned opt;
+       const char *port = "ftp";
+       /* socket to ftp server */
+       FILE *control_stream;
+       /* continue previous transfer (-c) */
+       ftp_host_info_t *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)(ftp_host_info_t *, FILE *, 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
+
+       /* Set default values */
+       server = xmalloc(sizeof(*server));
+       server->user = "anonymous";
+       server->password = "busybox@";
+
+       /*
+        * Decipher the command line
+        */
+#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
+       applet_long_options = ftpgetput_longopts;
+#endif
+       opt_complementary = "=3"; /* must have 3 params */
+       opt = getopt32(argv, "cvu:p:P:", &server->user, &server->password, &port);
+       argv += optind;
+
+       /* Process the non-option command line arguments */
+       if (opt & FTPGETPUT_OPT_CONTINUE) {
+               do_continue = 1;
+       }
+       if (opt & FTPGETPUT_OPT_VERBOSE) {
+               verbose_flag = 1;
+       }
+
+       /* We want to do exactly _one_ DNS lookup, since some
+        * sites (i.e. ftp.us.debian.org) use round-robin DNS
+        * and we want to connect to only one IP... */
+       server->lsa = xhost2sockaddr(argv[0], bb_lookup_port(port, "tcp", 21));
+       if (verbose_flag) {
+               printf("Connecting to %s (%s)\n", argv[0],
+                       xmalloc_sockaddr2dotted(&server->lsa->u.sa));
+       }
+
+       /*  Connect/Setup/Configure the FTP session */
+       control_stream = ftp_login(server);
+
+       return ftp_action(server, control_stream, argv[1], argv[2]);
+}
diff --git a/networking/hostname.c b/networking/hostname.c
new file mode 100644 (file)
index 0000000..93cbc96
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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)
+{
+       FILE *f;
+
+       if (!s)
+               return;
+       if (!isfile) {
+               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");
+               }
+       } else {
+               f = xfopen(s, "r");
+#define strbuf bb_common_bufsiz1
+               while (fgets(strbuf, sizeof(strbuf), f) != NULL) {
+                       if (strbuf[0] == '#') {
+                               continue;
+                       }
+                       chomp(strbuf);
+                       do_sethostname(strbuf, 0);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       fclose(f);
+       }
+}
+
+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 0;
+}
diff --git a/networking/httpd.c b/networking/httpd.c
new file mode 100644 (file)
index 0000000..2c5455b
--- /dev/null
@@ -0,0 +1,2411 @@
+/* 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 server can also be invoked as a url arg decoder and html text encoder
+ * as follows:
+ *  foo=`httpd -d $foo`           # decode "Hello%20World" as "Hello World"
+ *  bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
+ * Note that url encoding for arguments is not the same as html encoding for
+ * presentation.  -d decodes a url-encoded argument while -e encodes in html
+ * for page display.
+ *
+ * httpd.conf has the following format:
+ *
+ * A:172.20.         # Allow address from 172.20.0.0/16
+ * A:10.0.0.0/25     # Allow any address from 10.0.0.0-10.0.0.127
+ * A:10.0.0.0/255.255.255.128  # Allow any address that previous set
+ * A:127.0.0.1       # Allow local loopback connections
+ * D:*               # Deny from other IP connections
+ * 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   # running cgi.php scripts through an interpreter
+ *
+ * A/D may be as a/d or allow/deny - first char case insensitive
+ * Deny IP rules take precedence over allow rules.
+ *
+ *
+ * The Deny/Allow IP logic:
+ *
+ *  - Default is to allow all.  No addresses are denied unless
+ *         denied with a D: rule.
+ *  - Order of Deny/Allow rules is significant
+ *  - Deny rules take precedence over allow rules.
+ *  - If a deny all rule (D:*) is used it acts as a catch-all for unmatched
+ *       addresses.
+ *  - Specification of Allow all (A:*) is a no-op
+ *
+ * Example:
+ *   1. Allow only specified addresses
+ *     A:172.20          # Allow any address that begins with 172.20.
+ *     A:10.10.          # Allow any address that begins with 10.10.
+ *     A:127.0.0.1       # Allow local loopback connections
+ *     D:*               # Deny from other IP connections
+ *
+ *   2. Only deny specified addresses
+ *     D:1.2.3.        # deny from 1.2.3.0 - 1.2.3.255
+ *     D:2.3.4.        # deny from 2.3.4.0 - 2.3.4.255
+ *     A:*             # (optional line added for clarity)
+ *
+ * If a sub directory contains a config file it is parsed and merged with
+ * any existing settings as if it was appended to the original configuration.
+ *
+ * subdir paths are relative to the containing subdir and thus cannot
+ * affect the parent rules.
+ *
+ * Note that since the sub dir is parsed in the forked thread servicing the
+ * subdir http request, any merge is discarded when the process exits.  As a
+ * result, the subdir settings only have a lifetime of a single request.
+ *
+ * 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.
+ *
+ */
+
+#include "libbb.h"
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+#include <sys/sendfile.h>
+#endif
+
+//#define DEBUG 1
+#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 *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;)
+
+       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
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+       Htaccess *mime_a;       /* config mime types */
+#endif
+#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 configFile        (G.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 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        )
+#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)
+
+#if !ENABLE_FEATURE_HTTPD_RANGES
+enum {
+       range_start = 0,
+       range_end = MAXINT(off_t) - 1,
+       range_len = MAXINT(off_t),
+};
+#endif
+
+
+#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) ATTRIBUTE_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;
+}
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
+{
+       free_llist((has_next_ptr**)pptr);
+}
+#endif
+
+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.
+ *
+ * The first non-white character is examined to determine if the config line
+ * is one of the following:
+ *    .ext:mime/type   # new mime type not compiled into httpd
+ *    [adAD]:from      # ip address allow/deny, * for wildcard
+ *    /path:user:pass  # username/password
+ *    Ennn:error.html  # error page for status nnn
+ *    P:/url:[http://]hostname[:port]/new/path # reverse proxy
+ *
+ * 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 */
+#define FIRST_PARSE          0
+#define SUBDIR_PARSE         1
+#define SIGNALED_PARSE       2
+#define FIND_FROM_HTTPD_ROOT 3
+static void parse_conf(const char *path, int flag)
+{
+       FILE *f;
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       Htaccess *prev;
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+       Htaccess *cur;
+#endif
+       const char *cf = configFile;
+       char buf[160];
+       char *p0;
+       char *c, *p;
+       Htaccess_IP *pip;
+
+       /* discard old rules */
+       free_Htaccess_IP_list(&ip_a_d);
+       flg_deny_all = 0;
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+       /* retain previous auth and mime config only for subdir parse */
+       if (flag != SUBDIR_PARSE) {
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+               free_Htaccess_list(&g_auth);
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+               free_Htaccess_list(&mime_a);
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+               free_Htaccess_list(&script_i);
+#endif
+       }
+#endif
+
+       if (flag == SUBDIR_PARSE || cf == NULL) {
+               cf = alloca(strlen(path) + sizeof(httpd_conf) + 2);
+               sprintf((char *)cf, "%s/%s", path, httpd_conf);
+       }
+
+       while ((f = fopen(cf, "r")) == NULL) {
+               if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) {
+                       /* config file not found, no changes to config */
+                       return;
+               }
+               if (configFile && flag == FIRST_PARSE) /* if -c option given */
+                       bb_simple_perror_msg_and_die(cf);
+               flag = FIND_FROM_HTTPD_ROOT;
+               cf = httpd_conf;
+       }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       prev = g_auth;
+#endif
+       /* This could stand some work */
+       while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) {
+               c = NULL;
+               for (p = p0; *p0 != '\0' && *p0 != '#'; p0++) {
+                       if (!isspace(*p0)) {
+                               *p++ = *p0;
+                               if (*p0 == ':' && c == NULL)
+                                       c = p;
+                       }
+               }
+               *p = '\0';
+
+               /* test for empty or strange line */
+               if (c == NULL || *c == '\0')
+                       continue;
+               p0 = buf;
+               if (*p0 == 'd')
+                       *p0 = 'D';
+               if (*c == '*') {
+                       if (*p0 == 'D') {
+                               /* memorize deny all */
+                               flg_deny_all = 1;
+                       }
+                       /* skip default other "word:*" config lines */
+                       continue;
+               }
+
+               if (*p0 == 'a')
+                       *p0 = 'A';
+               if (*p0 == 'A' || *p0 == 'D') {
+                       /* storing current config IP line */
+                       pip = xzalloc(sizeof(Htaccess_IP));
+                       if (pip) {
+                               if (scan_ip_mask(c, &(pip->ip), &(pip->mask))) {
+                                       /* syntax IP{/mask} error detected, protect all */
+                                       *p0 = 'D';
+                                       pip->mask = 0;
+                               }
+                               pip->allow_deny = *p0;
+                               if (*p0 == 'D') {
+                                       /* Deny:from_IP move top */
+                                       pip->next = ip_a_d;
+                                       ip_a_d = pip;
+                               } else {
+                                       /* add to bottom A:form_IP config line */
+                                       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 && *p0 == 'E') {
+                       int i;
+                       /* error status code */
+                       int status = atoi(++p0);
+                       /* c already points at the character following ':' in parse loop */
+                       /* c = strchr(p0, ':'); c++; */
+                       if (status < HTTP_CONTINUE) {
+                               bb_error_msg("config error '%s' in '%s'", buf, cf);
+                               continue;
+                       }
+
+                       /* then error page; find matching status */
+                       for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
+                               if (http_response_type[i] == status) {
+                                       http_error_page[i] = concat_path_file((*c == '/') ? NULL : home_httpd, c);
+                                       break;
+                               }
+                       }
+                       continue;
+               }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+               if (flag == FIRST_PARSE && *p0 == 'P') {
+                       /* P:/url:[http://]hostname[:port]/new/path */
+                       char *url_from, *host_port, *url_to;
+                       Htaccess_Proxy *proxy_entry;
+
+                       url_from = c;
+                       host_port = strchr(c, ':');
+                       if (host_port == NULL) {
+                               bb_error_msg("config error '%s' in '%s'", buf, cf);
+                               continue;
+                       }
+                       *host_port++ = '\0';
+                       if (strncmp(host_port, "http://", 7) == 0)
+                               host_port += 7;
+                       if (*host_port == '\0') {
+                               bb_error_msg("config error '%s' in '%s'", buf, cf);
+                               continue;
+                       }
+                       url_to = strchr(host_port, '/');
+                       if (url_to == NULL) {
+                               bb_error_msg("config error '%s' in '%s'", buf, cf);
+                               continue;
+                       }
+                       *url_to = '\0';
+                       proxy_entry = xzalloc(sizeof(Htaccess_Proxy));
+                       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
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+               if (*p0 == '/') {
+                       /* make full path from httpd root / current_path / config_line_path */
+                       cf = (flag == SUBDIR_PARSE ? path : "");
+                       p0 = xmalloc(strlen(cf) + (c - buf) + 2 + strlen(c));
+                       c[-1] = '\0';
+                       sprintf(p0, "/%s%s", cf, buf);
+
+                       /* another call bb_simplify_path */
+                       cf = p = p0;
+
+                       do {
+                               if (*p == '/') {
+                                       if (*cf == '/') {    /* skip duplicate (or initial) slash */
+                                               continue;
+                                       }
+                                       if (*cf == '.') {
+                                               if (cf[1] == '/' || cf[1] == '\0') { /* remove extra '.' */
+                                                       continue;
+                                               }
+                                               if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == '\0')) {
+                                                       ++cf;
+                                                       if (p > p0) {
+                                                               while (*--p != '/') /* omit previous dir */;
+                                                       }
+                                                       continue;
+                                               }
+                                       }
+                               }
+                               *++p = *cf;
+                       } while (*++cf);
+
+                       if ((p == p0) || (*p != '/')) {      /* not a trailing slash */
+                               ++p;                             /* so keep last character */
+                       }
+                       *p = ':';
+                       strcpy(p + 1, c);
+               }
+#endif
+
+               if (*p0 == 'I') {
+                       index_page = xstrdup(c);
+                       continue;
+               }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+               /* storing current config line */
+               cur = xzalloc(sizeof(Htaccess) + strlen(p0));
+               cf = strcpy(cur->before_colon, p0);
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+               if (*p0 == '/')
+                       free(p0);
+#endif
+               c = strchr(cf, ':');
+               *c++ = '\0';
+               cur->after_colon = c;
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+               if (*cf == '.') {
+                       /* config .mime line move top for overwrite previous */
+                       cur->next = mime_a;
+                       mime_a = cur;
+                       continue;
+               }
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+               if (*cf == '*' && cf[1] == '.') {
+                       /* config script interpreter line move top for overwrite previous */
+                       cur->next = script_i;
+                       script_i = cur;
+                       continue;
+               }
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+               if (prev == NULL) {
+                       /* first line */
+                       g_auth = prev = cur;
+               } else {
+                       /* sort path, if current length eq or bigger then move up */
+                       Htaccess *prev_hti = g_auth;
+                       size_t l = strlen(cf);
+                       Htaccess *hti;
+
+                       for (hti = prev_hti; hti; hti = hti->next) {
+                               if (l >= strlen(hti->before_colon)) {
+                                       /* insert before hti */
+                                       cur->next = hti;
+                                       if (prev_hti != hti) {
+                                               prev_hti->next = cur;
+                                       } else {
+                                               /* insert as top */
+                                               g_auth = cur;
+                                       }
+                                       break;
+                               }
+                               if (prev_hti != hti)
+                                       prev_hti = prev_hti->next;
+                       }
+                       if (!hti) {       /* not inserted, add to bottom */
+                               prev->next = cur;
+                               prev = cur;
+                       }
+               }
+#endif /* BASIC_AUTH */
+#endif /* BASIC_AUTH || MIME_TYPES || SCRIPT_INTERPR */
+        } /* 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 strdup() the argument if they do not want the
+ * argument modified.  The return is the original pointer, allowing this
+ * function to be easily used as arguments to other functions.
+ *
+ * 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) ATTRIBUTE_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);
+       ndelay_on(0);
+       while (read(0, 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(0);
+       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)) {
+               strcat(iobuf, "\r\n");
+               len += 2;
+
+               if (DEBUG)
+                       fprintf(stderr, "headers: '%s'\n", iobuf);
+               full_write(1, 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(1, iobuf, len) != len) {
+               if (verbose > 1)
+                       bb_perror_msg("error");
+               log_and_exit();
+       }
+}
+
+static void send_headers_and_exit(int responseNum) ATTRIBUTE_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;
+
+       while (1) {
+               if (hdr_cnt <= 0) {
+                       hdr_cnt = safe_read(0, 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';
+                       return count;
+               }
+               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) ATTRIBUTE_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(0, hdr_buf, count);
+                       count = safe_read(0, 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(1, HTTP_200, sizeof(HTTP_200)-1);
+                                               full_write(1, 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(1, 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(1, 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(1, 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) ATTRIBUTE_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 *fullpath;
+       char *script;
+       char *purl;
+       int pid;
+
+       /*
+        * We are mucking with environment _first_ and then vfork/exec,
+        * this allows us to use vfork safely. Parent don't care about
+        * these environment changes anyway.
+        */
+
+       /*
+        * Find PATH_INFO.
+        */
+       purl = xstrdup(url);
+       script = purl;
+       while ((script = strchr(script + 1, '/')) != NULL) {
+               /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */
+               struct stat sb;
+
+               *script = '\0';
+               if (!is_directory(purl + 1, 1, &sb)) {
+                       /* not directory, found script.cgi/PATH_INFO */
+                       *script = '/';
+                       break;
+               }
+               *script = '/';          /* is directory, find next '/' */
+       }
+       setenv1("PATH_INFO", script);   /* set /PATH_INFO or "" */
+       setenv1("REQUEST_METHOD", request);
+       if (g_query) {
+               putenv(xasprintf("%s=%s?%s", "REQUEST_URI", purl, g_query));
+       } else {
+               setenv1("REQUEST_URI", purl);
+       }
+       if (script != NULL)
+               *script = '\0';         /* cut off /PATH_INFO */
+
+       /* SCRIPT_FILENAME required by PHP in CGI mode */
+       fullpath = concat_path_file(home_httpd, purl);
+       setenv1("SCRIPT_FILENAME", fullpath);
+       /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
+       setenv1("SCRIPT_NAME", purl);
+       /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
+        * QUERY_STRING: The information which follows the ? in the URL
+        * which referenced this script. This is the query information.
+        * It should not be decoded in any fashion. This variable
+        * should always be set when there is query information,
+        * regardless of command line decoding. */
+       /* (Older versions of bbox 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 (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);
+
+       xpiped_pair(fromCgi);
+       xpiped_pair(toCgi);
+
+       pid = vfork();
+       if (pid < 0) {
+               /* TODO: log perror? */
+               log_and_exit();
+       }
+
+       if (!pid) {
+               /* Child process */
+               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); */
+
+               script = strrchr(fullpath, '/');
+               //fullpath is a result of concat_path_file and always has '/'
+               //if (!script)
+               //      goto error_execing_cgi;
+               *script = '\0';
+               /* chdiring to script's dir */
+               if (chdir(script == fullpath ? "/" : fullpath) == 0) {
+                       char *argv[3];
+
+                       *script++ = '/'; /* repair fullpath */
+                       /* 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 */
+                                                       fullpath = cur->after_colon;
+                                                       argv[0] = cur->after_colon;
+                                                       argv[1] = script;
+                                                       argv[2] = NULL;
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+#endif
+                       /* restore default signal dispositions for CGI process */
+                       signal(SIGCHLD, SIG_DFL);
+                       signal(SIGPIPE, SIG_DFL);
+                       signal(SIGHUP, SIG_DFL);
+
+                       execv(fullpath, argv);
+                       if (verbose)
+                               bb_perror_msg("exec %s", fullpath);
+               } else if (verbose) {
+                       bb_perror_msg("chdir %s", fullpath);
+               }
+ //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 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 f;
+       const char *const *table;
+       const char *try_suffix;
+       ssize_t count;
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+       off_t offset;
+#endif
+
+       /* 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) {
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+               Htaccess *cur;
+#endif
+               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;
+                               }
+                       }
+               }
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+               for (cur = mime_a; cur; cur = cur->next) {
+                       if (strcmp(cur->before_colon, suffix) == 0) {
+                               found_mime_type = cur->after_colon;
+                               break;
+                       }
+               }
+#endif
+       }
+
+       if (DEBUG)
+               bb_error_msg("sending file '%s' content-type: %s",
+                       url, found_mime_type);
+
+       f = open(url, O_RDONLY);
+       if (f < 0) {
+               if (DEBUG)
+                       bb_perror_msg("cannot 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 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(f, range_start, SEEK_SET) != range_start
+               ) {
+                       lseek(f, 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
+       offset = range_start;
+       do {
+               /* 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(1, f, &offset, sz);
+               if (count < 0) {
+                       if (offset == range_start)
+                               goto fallback;
+                       goto fin;
+               }
+               USE_FEATURE_HTTPD_RANGES(range_len -= sz;)
+       } while (count > 0 && range_len);
+       log_and_exit();
+
+ fallback:
+#endif
+       while ((count = safe_read(f, iobuf, IOBUF_SIZE)) > 0) {
+               ssize_t n;
+               USE_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;)
+               n = full_write(1, iobuf, count);
+               if (count != n)
+                       break;
+               USE_FEATURE_HTTPD_RANGES(range_len -= count;)
+               if (!range_len)
+                       break;
+       }
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+ fin:
+#endif
+       if (count < 0 && verbose > 1)
+               bb_perror_msg("error");
+       log_and_exit();
+}
+
+static int checkPermIP(void)
+{
+       Htaccess_IP *cur;
+
+       /* This could stand some work */
+       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';   /* Allow/Deny */
+       }
+
+       /* if unconfigured, return 1 - access from all */
+       return !flg_deny_all;
+}
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+/*
+ * Check the permission file for access password protected.
+ *
+ * If config file isn't present, everything is allowed.
+ * Entries are of the form you can see example from header source
+ *
+ * path      The file path.
+ * request   User information to validate.
+ *
+ * Returns 1 if request is OK.
+ */
+static int checkPerm(const char *path, const char *request)
+{
+       Htaccess *cur;
+       const char *p;
+       const char *p0;
+
+       const char *prev = NULL;
+
+       /* This could stand some work */
+       for (cur = g_auth; cur; cur = cur->next) {
+               size_t l;
+
+               p0 = cur->before_colon;
+               if (prev != NULL && strcmp(prev, p0) != 0)
+                       continue;       /* find next identical */
+               p = cur->after_colon;
+               if (DEBUG)
+                       fprintf(stderr, "checkPerm: '%s' ? '%s'\n", p0, request);
+
+               l = strlen(p0);
+               if (strncmp(p0, path, l) == 0
+                && (l == 1 || path[l] == '/' || path[l] == '\0')
+               ) {
+                       char *u;
+                       /* path match found.  Check request */
+                       /* for check next /path:user:password */
+                       prev = p0;
+                       u = strchr(request, ':');
+                       if (u == NULL) {
+                               /* bad request, ':' required */
+                               break;
+                       }
+
+                       if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
+                               char *cipher;
+                               char *pp;
+
+                               if (strncmp(p, request, u - request) != 0) {
+                                       /* user doesn't match */
+                                       continue;
+                               }
+                               pp = strchr(p, ':');
+                               if (pp && pp[1] == '$' && pp[2] == '1'
+                                && pp[3] == '$' && pp[4]
+                               ) {
+                                       pp++;
+                                       cipher = pw_encrypt(u+1, pp);
+                                       if (strcmp(cipher, pp) == 0)
+                                               goto set_remoteuser_var;   /* Ok */
+                                       /* unauthorized */
+                                       continue;
+                               }
+                       }
+
+                       if (strcmp(p, request) == 0) {
+ set_remoteuser_var:
+                               remoteuser = strdup(request);
+                               if (remoteuser)
+                                       remoteuser[u - request] = '\0';
+                               return 1;   /* Ok */
+                       }
+                       /* unauthorized */
+               }
+       } /* for */
+
+       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 exit_on_signal(int sig) ATTRIBUTE_NORETURN;
+static void exit_on_signal(int sig ATTRIBUTE_UNUSED)
+{
+       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) ATTRIBUTE_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;
+       int ip_allowed;
+#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
+       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
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       int credentials = -1;  /* if not required this is Ok */
+#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) {
+               rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa);
+       }
+       if (verbose) {
+               /* this trick makes -v logging much simpler */
+               applet_name = rmt_ip_str;
+               if (verbose > 2)
+                       bb_error_msg("connected");
+       }
+
+       /* Install timeout handler */
+       signal_no_SA_RESTART_empty_mask(SIGALRM, exit_on_signal);
+       alarm(HEADER_READ_TIMEOUT);
+
+       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 and reducing trailing slash and protect out 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';       /* so keep last character */
+       tptr = urlp;          /* end ptr */
+
+       /* If URL is a directory, add '/' */
+       if (tptr[-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 be having 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) {
+                       alarm(HEADER_READ_TIMEOUT);
+                       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 = strdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
+                       } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
+                               content_type = strdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
+                       } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
+                               referer = strdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
+                       } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
+                               user_agent = strdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
+                       }
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+                       if (STRNCASECMP(iobuf, "Authorization:") == 0) {
+                               /* We only allow Basic credentials.
+                                * It shows up as "Authorization: Basic <userid:password>" where
+                                * the userid:password 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);
+                               credentials = checkPerm(urlcopy, tptr);
+                       }
+#endif          /* FEATURE_HTTPD_BASIC_AUTH */
+#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 == 0) {
+               /* protect listing [/path]/httpd_conf or IP deny */
+               send_headers_and_exit(HTTP_FORBIDDEN);
+       }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+       if (credentials <= 0 && checkPerm(urlcopy, ":") == 0) {
+               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 disctinct 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) ATTRIBUTE_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 */
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+                       /* Do not reload config on HUP */
+                       signal(SIGHUP, SIG_IGN);
+#endif
+                       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) ATTRIBUTE_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 */
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+                       /* Do not reload config on HUP */
+                       signal(SIGHUP, SIG_IGN);
+#endif
+                       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) ATTRIBUTE_NORETURN;
+static void mini_httpd_inetd(void)
+{
+       len_and_sockaddr fromAddr;
+
+       fromAddr.len = LSA_SIZEOF_SA;
+       getpeername(0, &fromAddr.u.sa, &fromAddr.len);
+       handle_incoming_and_exit(&fromAddr);
+}
+
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+static void sighup_handler(int sig)
+{
+       parse_conf(default_path_httpd_conf, sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE);
+
+       signal_SA_RESTART_empty_mask(SIGHUP, sighup_handler);
+}
+#endif
+
+enum {
+       c_opt_config_file = 0,
+       d_opt_decode_url,
+       h_opt_home_httpd,
+       USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
+       USE_FEATURE_HTTPD_BASIC_AUTH(    r_opt_realm     ,)
+       USE_FEATURE_HTTPD_AUTH_MD5(      m_opt_md5       ,)
+       USE_FEATURE_HTTPD_SETUID(        u_opt_setuid    ,)
+       p_opt_port      ,
+       p_opt_inetd     ,
+       p_opt_foreground,
+       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 ATTRIBUTE_UNUSED, 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 can end up
+        * relative too. */
+       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",
+                       &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$"));
+               return 0;
+       }
+#endif
+#if ENABLE_FEATURE_HTTPD_SETUID
+       if (opt & OPT_SETUID) {
+               if (!get_uidgid(&ugid, s_ugid, 1))
+                       bb_error_msg_and_die("unknown user[:group] "
+                                               "name '%s'", 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 /*was #if ENABLE_FEATURE_HTTPD_CGI*/
+       /* 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 strdup(p): */
+               clearenv();
+               if (p)
+                       putenv(p - 5);
+//             if (!(opt & OPT_INETD))
+//                     setenv_long("SERVER_PORT", ???);
+       }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+       if (!(opt & OPT_INETD))
+               sighup_handler(0);
+#endif
+       parse_conf(default_path_httpd_conf, FIRST_PARSE);
+
+       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..fd64af3
--- /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(1, 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/ifconfig.c b/networking/ifconfig.c
new file mode 100644 (file)
index 0000000..fcbeb24
--- /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 */
+       safe_strncpy(ifr.ifr_name, *argv, IFNAMSIZ);
+
+       /* Process the remaining arguments. */
+       while (*++argv != (char *) NULL) {
+               p = *argv;
+               mask = N_MASK;
+               if (*p == '-') {        /* If the arg starts with '-'... */
+                       ++p;            /*    advance past it and */
+                       mask = M_MASK;  /*    set the appropriate mask. */
+               }
+               for (op = OptArray; op->name; op++) {   /* Find table entry. */
+                       if (strcmp(p, op->name) == 0) { /* If name matches... */
+                               mask &= op->flags;
+                               if (mask)       /* set the mask and go. */
+                                       goto FOUND_ARG;
+                               /* If we get here, there was a valid arg with an */
+                               /* invalid '-' prefix. */
+                               bb_error_msg_and_die("bad: '%s'", p-1);
+                       }
+               }
+
+               /* We fell through, so treat as possible hostname. */
+               a1op = Arg1Opt + 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. */
+                                               if (strcmp("ether", *argv) || !*++argv)
+                                                       bb_show_usage();
+                                               /*safe_strncpy(host, *argv, sizeof(host));*/
+                                               host = *argv;
+                                               if (in_ether(host, &sa))
+                                                       bb_error_msg_and_die("invalid hw-addr %s", host);
+                                               p = (char *) &sa;
+                                       }
+#endif
+                                       memcpy( (((char *)&ifr) + a1op->ifr_offset),
+                                                  p, sizeof(struct sockaddr));
+                               } else {
+                                       /* FIXME: error check?? */
+                                       unsigned long i = strtoul(*argv, NULL, 0);
+                                       p = ((char *)&ifr) + a1op->ifr_offset;
+#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
+                                       if (mask & A_MAP_TYPE) {
+                                               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 = 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..1e3d5bb
--- /dev/null
@@ -0,0 +1,621 @@
+/* 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_arp.h>
+#include <linux/if_bonding.h>
+#include <linux/sockios.h>
+
+typedef unsigned long long 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)
+
+
+static void get_drv_info(char *master_ifname);
+static int get_if_settings(char *ifname, struct dev_data *dd);
+static int get_slave_flags(char *slave_ifname);
+static int set_master_hwaddr(char *master_ifname, struct sockaddr *hwaddr);
+static int set_slave_hwaddr(char *slave_ifname, struct sockaddr *hwaddr);
+static int set_slave_mtu(char *slave_ifname, int mtu);
+static int set_if_flags(char *ifname, short flags);
+static int set_if_up(char *ifname, short flags);
+static int set_if_down(char *ifname, short flags);
+static int clear_if_addr(char *ifname);
+static int set_if_addr(char *master_ifname, char *slave_ifname);
+static void change_active(char *master_ifname, char *slave_ifname);
+static int enslave(char *master_ifname, char *slave_ifname);
+static int release(char *master_ifname, char *slave_ifname);
+
+
+int ifenslave_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifenslave_main(int argc ATTRIBUTE_UNUSED, 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" No_argument "c"
+               "detach"        No_argument "d"
+               "force"         No_argument "f"
+       ;
+
+       applet_long_options = ifenslave_longopts;
+#endif
+       opt = getopt32(argv, "cdf");
+       argv += optind;
+       if (opt & (opt-1)) /* options check */
+               bb_show_usage();
+
+       /* Copy the interface name */
+       master_ifname = *argv++;
+
+       /* No remaining args means 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)) {
+                       display_interfaces(slave_ifname);
+                       return 2; /* why? */
+               }
+               /* A single arg means show the
+                * configuration for this interface
+                */
+               display_interfaces(master_ifname);
+               return EXIT_SUCCESS;
+       }
+
+       res = get_if_settings(master_ifname, &master);
+       if (res) {
+               /* 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);
+
+       /* Only for enslaving */
+       if (!(opt & (OPT_c|OPT_d))) {
+               sa_family_t master_family = master.hwaddr.ifr_hwaddr.sa_family;
+
+               /* The family '1' is ARPHRD_ETHER for ethernet. */
+               if (master_family != 1 && !(opt & OPT_f)) {
+                       bb_error_msg_and_die(
+                               "%s is not ethernet-like (-f overrides)",
+                               master_ifname);
+               }
+       }
+
+       /* Accepts only one slave */
+       if (opt & OPT_c) {
+               /* change active slave */
+               res = get_slave_flags(slave_ifname);
+               if (res) {
+                       bb_perror_msg_and_die(
+                               "%s: can't get flags", slave_ifname);
+               }
+               change_active(master_ifname, slave_ifname);
+               return EXIT_SUCCESS;
+       }
+
+       /* Accept 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(
+                                       "master %s, slave %s: "
+                                       "can't release",
+                                       master_ifname, slave_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(
+                                       "master %s, slave %s: "
+                                       "can't enslave",
+                                       master_ifname, slave_ifname);
+                               res = rv;
+                       }
+               }
+       } while ((slave_ifname = *argv++) != NULL);
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               close(skfd);
+       }
+
+       return res;
+}
+
+static void get_drv_info(char *master_ifname)
+{
+       struct ifreq ifr;
+       struct ethtool_drvinfo info;
+
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_name, master_ifname, IFNAMSIZ);
+       ifr.ifr_data = (caddr_t)&info;
+
+       info.cmd = ETHTOOL_GDRVINFO;
+       strncpy(info.driver, "ifenslave", 32);
+       snprintf(info.fw_version, 32, "%d", BOND_ABI_VERSION);
+
+       if (ioctl(skfd, SIOCETHTOOL, &ifr) < 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);
+
+       return;
+}
+
+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(ifr.ifr_name, master_ifname, IFNAMSIZ);
+       strncpy(ifr.ifr_slave, slave_ifname, IFNAMSIZ);
+       if (ioctl(skfd, SIOCBONDCHANGEACTIVE, &ifr) < 0
+        && ioctl(skfd, BOND_CHANGE_ACTIVE_OLD, &ifr) < 0
+       ) {
+               bb_perror_msg_and_die(
+                       "master %s, slave %s: can't "
+                       "change active",
+                       master_ifname, slave_ifname);
+       }
+}
+
+static 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_slave_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
+                        */
+                       res = set_slave_hwaddr(slave_ifname,
+                                              &(master.hwaddr.ifr_hwaddr));
+                       if (res) {
+                               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
+                        */
+                       res = set_if_up(slave_ifname, slave.flags.ifr_flags);
+                       if (res)
+                               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
+                        */
+                       res = set_if_down(master_ifname, master.flags.ifr_flags);
+                       if (res)
+                               goto undo_mtu;
+               }
+
+               res = set_master_hwaddr(master_ifname,
+                                       &(slave.hwaddr.ifr_hwaddr));
+               if (res) {
+                       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
+                        */
+                       res = set_if_up(master_ifname, master.flags.ifr_flags);
+                       if (res)
+                               goto undo_master_mac;
+               }
+
+               hwaddr_set = 1;
+       }
+
+       /* Do the real thing */
+       strncpy(ifr.ifr_name, master_ifname, IFNAMSIZ);
+       strncpy(ifr.ifr_slave, slave_ifname, IFNAMSIZ);
+       if (ioctl(skfd, SIOCBONDENSLAVE, &ifr) < 0
+        && ioctl(skfd, BOND_ENSLAVE_OLD, &ifr) < 0
+       ) {
+               res = 1;
+       }
+
+       if (res)
+               goto undo_master_mac;
+
+       return 0;
+
+/* rollback (best effort) */
+ undo_master_mac:
+       set_master_hwaddr(master_ifname, &(master.hwaddr.ifr_hwaddr));
+       hwaddr_set = 0;
+       goto undo_mtu;
+ undo_slave_mac:
+       set_slave_hwaddr(slave_ifname, &(slave.hwaddr.ifr_hwaddr));
+ undo_mtu:
+       set_slave_mtu(slave_ifname, slave.mtu.ifr_mtu);
+       return res;
+}
+
+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(ifr.ifr_name, master_ifname, IFNAMSIZ);
+       strncpy(ifr.ifr_slave, slave_ifname, IFNAMSIZ);
+       if (ioctl(skfd, SIOCBONDRELEASE, &ifr) < 0
+        && ioctl(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_slave_mtu(slave_ifname, 1500);
+
+       return res;
+}
+
+static int get_if_settings(char *ifname, struct dev_data *dd)
+{
+       int res;
+
+       strncpy(dd->mtu.ifr_name, ifname, IFNAMSIZ);
+       res = ioctl(skfd, SIOCGIFMTU, &dd->mtu);
+       strncpy(dd->flags.ifr_name, ifname, IFNAMSIZ);
+       res |= ioctl(skfd, SIOCGIFFLAGS, &dd->flags);
+       strncpy(dd->hwaddr.ifr_name, ifname, IFNAMSIZ);
+       res |= ioctl(skfd, SIOCGIFHWADDR, &dd->hwaddr);
+
+       return res;
+}
+
+static int get_slave_flags(char *slave_ifname)
+{
+       strncpy(slave.flags.ifr_name, slave_ifname, IFNAMSIZ);
+       return ioctl(skfd, SIOCGIFFLAGS, &slave.flags);
+}
+
+static int set_master_hwaddr(char *master_ifname, struct sockaddr *hwaddr)
+{
+       struct ifreq ifr;
+
+       strncpy(ifr.ifr_name, master_ifname, IFNAMSIZ);
+       memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(struct sockaddr));
+       return ioctl(skfd, SIOCSIFHWADDR, &ifr);
+}
+
+static int set_slave_hwaddr(char *slave_ifname, struct sockaddr *hwaddr)
+{
+       struct ifreq ifr;
+
+       strncpy(ifr.ifr_name, slave_ifname, IFNAMSIZ);
+       memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(struct sockaddr));
+       return ioctl(skfd, SIOCSIFHWADDR, &ifr);
+}
+
+static int set_slave_mtu(char *slave_ifname, int mtu)
+{
+       struct ifreq ifr;
+
+       ifr.ifr_mtu = mtu;
+       strncpy(ifr.ifr_name, slave_ifname, IFNAMSIZ);
+       return ioctl(skfd, SIOCSIFMTU, &ifr);
+}
+
+static int set_if_flags(char *ifname, short flags)
+{
+       struct ifreq ifr;
+
+       ifr.ifr_flags = flags;
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+       return ioctl(skfd, SIOCSIFFLAGS, &ifr);
+}
+
+static int set_if_up(char *ifname, short 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, short 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;
+
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+       ifr.ifr_addr.sa_family = AF_INET;
+       memset(ifr.ifr_addr.sa_data, 0, sizeof(ifr.ifr_addr.sa_data));
+       return ioctl(skfd, SIOCSIFADDR, &ifr);
+}
+
+static int set_if_addr(char *master_ifname, char *slave_ifname)
+{
+       static const struct {
+               int g_ioctl;
+               int s_ioctl;
+       } ifra[] = {
+               { SIOCGIFADDR,    SIOCSIFADDR    },
+               { SIOCGIFDSTADDR, SIOCSIFDSTADDR },
+               { SIOCGIFBRDADDR, SIOCSIFBRDADDR },
+               { SIOCGIFNETMASK, SIOCSIFNETMASK },
+       };
+
+       struct ifreq ifr;
+       int res;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(ifra); i++) {
+               strncpy(ifr.ifr_name, master_ifname, IFNAMSIZ);
+               res = ioctl(skfd, ifra[i].g_ioctl, &ifr);
+               if (res < 0) {
+                       ifr.ifr_addr.sa_family = AF_INET;
+                       memset(ifr.ifr_addr.sa_data, 0,
+                              sizeof(ifr.ifr_addr.sa_data));
+               }
+
+               strncpy(ifr.ifr_name, slave_ifname, IFNAMSIZ);
+               res = ioctl(skfd, ifra[i].s_ioctl, &ifr);
+               if (res < 0)
+                       return res;
+       }
+
+       return 0;
+}
diff --git a/networking/ifupdown.c b/networking/ifupdown.c
new file mode 100644 (file)
index 0000000..1746819
--- /dev/null
@@ -0,0 +1,1277 @@
+/* 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 <getopt.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 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) {
+                                       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%]][[ address %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%]][[ address %hwaddress%]] %iface% up", ifd, exec);
+       result += execute("[[ip route add default via %gateway% dev %iface%]]", ifd, exec);
+       return ((result == 3) ? 3 : 0);
+#else
+       /* ifconfig said to set iface up before it processes hw %hwaddress%,
+        * which then of course fails. Thus we run two separate ifconfig */
+       result = execute("ifconfig %iface%[[ hw %hwaddress%]][[ media %media%]][[ mtu %mtu%]] up",
+                               ifd, exec);
+       result += execute("ifconfig %iface% %address% netmask %netmask%"
+                               "[[ broadcast %broadcast%]][[ pointopoint %pointopoint%]] ",
+                               ifd, exec);
+       result += execute("[[route add default gw %gateway% %iface%]]", ifd, exec);
+       return ((result == 3) ? 3 : 0);
+#endif
+}
+
+static int static_down(struct interface_defn_t *ifd, execfn *exec)
+{
+       int result;
+#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);
+       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 -R -n -p /var/run/udhcpc.%iface%.pid -i %iface%[[ -H %hostname%]][[ -c %clientid%]][[ -s %script%]]",
+               "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)
+{
+       int i;
+#if ENABLE_FEATURE_IFUPDOWN_IP
+       /* ip doesn't up iface when it configures it (unlike ifconfig) */
+       if (!execute("ip link set %iface% 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 %iface% up", ifd, exec))
+               return 0;
+#endif
+       return execute("udhcpc -R -n -p /var/run/udhcpc.%iface%.pid "
+                       "-i %iface%[[ -H %hostname%]][[ -c %clientid%]][[ -s %script%]]",
+                       ifd, exec);
+}
+#else
+static int dhcp_up(struct interface_defn_t *ifd ATTRIBUTE_UNUSED,
+               execfn *exec ATTRIBUTE_UNUSED)
+{
+       return 0; /* no dhcp support */
+}
+#endif
+
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+static int dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+       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
+static int dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+       return execute("kill "
+                      "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec);
+}
+#else
+static int dhcp_down(struct interface_defn_t *ifd ATTRIBUTE_UNUSED,
+               execfn *exec ATTRIBUTE_UNUSED)
+{
+       return 0; /* no dhcp support */
+}
+#endif
+
+static int manual_up_down(struct interface_defn_t *ifd ATTRIBUTE_UNUSED, execfn *exec ATTRIBUTE_UNUSED)
+{
+       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 const llist_t *find_list_string(const llist_t *list, const char *string)
+{
+       if (string == NULL)
+               return NULL;
+
+       while (list) {
+               if (strcmp(list->data, string) == 0) {
+                       return list;
+               }
+               list = list->link;
+       }
+       return NULL;
+}
+
+static struct interfaces_file_t *read_interfaces(const char *filename)
+{
+       /* 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 "#" character in the very first column 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(filename, "r");
+
+       while ((buf = xmalloc_getline(f)) != NULL) {
+#if ENABLE_DESKTOP
+               /* Trailing "\" concatenates lines */
+               char *p;
+               while ((p = last_char_is(buf, '\\')) != NULL) {
+                       *p = '\0';
+                       rest_of_line = xmalloc_getline(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 || *buf == '#') {
+                       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) {
+                               if (currmap->n_matches >= currmap->max_matches) {
+                                       currmap->max_matches = currmap->max_matches * 2 + 1;
+                                       currmap->match = xrealloc(currmap->match,
+                                               sizeof(*currmap->match) * currmap->max_matches);
+                               }
+                               currmap->match[currmap->n_matches++] = xstrdup(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 (find_list_string(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, 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_getline(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(CONFIG_IFUPDOWN_IFSTATE_PATH, "r");
+
+       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;
+               unsigned 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(CONFIG_IFUPDOWN_IFSTATE_PATH, "w");
+                       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..5cdfe0a
--- /dev/null
@@ -0,0 +1,1624 @@
+/* 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;                     /* whem 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 prev_maxsock;
+       int maxsock;
+       unsigned max_concurrency;
+       smallint alarm_armed;
+       uid_t real_uid; /* user ID who ran us */
+       unsigned config_lineno;
+       const char *config_filename;
+       FILE *fconfig;
+       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 prev_maxsock    (G.prev_maxsock   )
+#define maxsock         (G.maxsock        )
+#define max_concurrency (G.max_concurrency)
+#define alarm_armed     (G.alarm_armed    )
+#define real_uid        (G.real_uid       )
+#define config_lineno   (G.config_lineno  )
+#define config_filename (G.config_filename)
+#define fconfig         (G.fconfig        )
+#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)maxsock > rlim_ofile_cur - FD_MARGIN)
+                               bump_nofile();
+               }
+       }
+}
+
+static void recalculate_maxsock(void)
+{
+       int fd = 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 (fconfig != NULL)
+               fclose(fconfig);
+       config_lineno = 0;
+       fconfig = fopen_or_warn(config_filename, "r");
+       return (fconfig != NULL);
+}
+
+static void close_config_file(void)
+{
+       if (fconfig) {
+               fclose(fconfig);
+               fconfig = NULL;
+       }
+}
+
+static char *next_line(void)
+{
+       if (fgets(line, LINE_SIZE, fconfig) == NULL)
+               return NULL;
+       config_lineno++;
+       *strchrnul(line, '\n') = '\0';
+       return line;
+}
+
+static char *next_word(char **cpp)
+{
+       char *start;
+       char *cp = *cpp;
+
+       if (cp == NULL)
+               return NULL;
+ again:
+       while (*cp == ' ' || *cp == '\t')
+               cp++;
+       if (*cp == '\0') {
+               int c = getc(fconfig);
+               ungetc(c, fconfig);
+               if (c == ' ' || c == '\t') {
+                       cp = next_line();
+                       if (cp)
+                               goto again;
+               }
+               *cpp = NULL;
+               return NULL;
+       }
+       start = cp;
+       while (*cp && *cp != ' ' && *cp != '\t')
+               cp++;
+       if (*cp != '\0')
+               *cp++ = '\0';
+
+       *cpp = cp;
+       return start;
+}
+
+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 NOINLINE servtab_t *parse_one_line(void)
+{
+       int argc;
+       char *p, *cp, *arg;
+       char *hostdelim;
+       servtab_t *sep;
+       servtab_t *nsep;
+ new:
+       sep = new_servtab();
+ more:
+       while ((cp = next_line()) && *cp == '#')
+               continue; /* skip comment lines */
+       if (cp == NULL) {
+               free(sep);
+               return NULL;
+       }
+
+       arg = next_word(&cp);
+       if (arg == NULL) /* a blank line. */
+               goto more;
+
+       /* [host:]service socktype proto wait user[:group] prog [args] */
+       /* Check for "host:...." line */
+       hostdelim = strrchr(arg, ':');
+       if (hostdelim) {
+               *hostdelim = '\0';
+               sep->se_local_hostname = xstrdup(arg);
+               arg = hostdelim + 1;
+               if (*arg == '\0') {
+                       arg = next_word(&cp);
+                       if (arg == NULL) {
+                               /* 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] */
+       arg = next_word(&cp);
+       if (arg == NULL) {
+ parse_err:
+               bb_error_msg("parse error on line %u, line is ignored",
+                               config_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"
+                       , arg)];
+       }
+
+       /* {unix,[rpc/]{tcp,udp}[6]} wait user[:group] prog [args] */
+       sep->se_proto = arg = xstrdup(next_word(&cp));
+       if (arg == NULL)
+               goto parse_err;
+       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 || 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 = next_word(&cp);
+       if (arg == NULL)
+               goto parse_err;
+       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(next_word(&cp));
+       if (sep->se_user == NULL)
+               goto parse_err;
+       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(next_word(&cp));
+       if (sep->se_program == NULL)
+               goto parse_err;
+#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)
+       ) {
+               int 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 = next_word(&cp)) != 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;
+       }
+
+       /* 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 ATTRIBUTE_UNUSED)
+{
+       servtab_t *sep, *cp, **sepp;
+       len_and_sockaddr *lsa;
+       sigset_t omask;
+       unsigned n;
+       uint16_t port;
+
+       if (!reopen_config_file())
+               return;
+       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);
+}
+
+static void reap_child(int sig ATTRIBUTE_UNUSED)
+{
+       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) {
+                               /* 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);
+                       }
+       }
+       errno = save_errno;
+}
+
+static void retry_network_setup(int sig ATTRIBUTE_UNUSED)
+{
+       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
+               }
+       }
+}
+
+static void clean_up_and_exit(int sig ATTRIBUTE_UNUSED)
+{
+       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(0);
+}
+
+int inetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int inetd_main(int argc ATTRIBUTE_UNUSED, 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_NOWAIT, 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 */
+               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 = 0; /* 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(1);
+                               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 user", sep->se_user);
+                               goto do_exit1;
+                       }
+                       if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) {
+                               bb_error_msg("%s: no such group", sep->se_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(1);
+               } /* for (sep = servtab...) */
+       } /* for (;;) */
+}
+
+/*
+ * 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 ATTRIBUTE_UNUSED)
+{
+#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! */
+       static const char *const args[] = { "cat", NULL };
+       /* move network socket to stdin */
+       xmove_fd(s, STDIN_FILENO);
+       xdup2(STDIN_FILENO, STDOUT_FILENO);
+       /* no error messages please... */
+       xmove_fd(xopen("/dev/null", O_WRONLY), STDERR_FILENO);
+       BB_EXECVP("cat", (char**)args);
+       /* on failure we return to main, which does exit(1) */
+#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. MMU arches only. */
+/* ARGSUSED */
+static void discard_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED)
+{
+#if BB_MMU
+       while (safe_read(s, line, LINE_SIZE) > 0)
+               continue;
+#else
+       /* We are after vfork here! */
+       static const char *const args[] = { "dd", "of=/dev/null", NULL };
+       /* move network socket to stdin */
+       xmove_fd(s, STDIN_FILENO);
+       xdup2(STDIN_FILENO, STDOUT_FILENO);
+       /* no error messages */
+       xmove_fd(xopen("/dev/null", O_WRONLY), STDERR_FILENO);
+       BB_EXECVP("dd", (char**)args);
+       /* on failure we return to main, which does exit(1) */
+#endif
+}
+/* ARGSUSED */
+static void discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED)
+{
+       /* 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 ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED)
+{
+       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..44bd8d3
--- /dev/null
@@ -0,0 +1,1202 @@
+/* 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_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 *INET_sprint(struct sockaddr *sap, int numeric)
+{
+       static char *buff;
+
+       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 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 *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 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 *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 *UNSPEC_sprint(struct sockaddr *sap, int numeric ATTRIBUTE_UNUSED)
+{
+       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 *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));
+       safe_strncpy(new->name, name, IFNAMSIZ);
+       nextp = ife ? &ife->next : &int_list;
+       new->prev = ife;
+       new->next = *nextp;
+       if (new->next)
+               new->next->prev = new;
+       else
+               int_last = new;
+       *nextp = new;
+       return new;
+}
+
+static 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 inline 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 == 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(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) {
+               close(skfd);
+               return -1;
+       }
+       ife->flags = ifr.ifr_flags;
+
+       strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       ife->metric = 0;
+       if (ioctl(skfd, SIOCGIFMETRIC, &ifr) >= 0)
+               ife->metric = ifr.ifr_metric;
+
+       strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       ife->mtu = 0;
+       if (ioctl(skfd, SIOCGIFMTU, &ifr) >= 0)
+               ife->mtu = ifr.ifr_mtu;
+
+       memset(&ife->map, 0, sizeof(struct ifmap));
+#ifdef SIOCGIFMAP
+       strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0)
+               ife->map = ifr.ifr_map;
+#endif
+
+#ifdef HAVE_TXQUEUELEN
+       strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+               memset(&ife->dstaddr, 0, sizeof(struct sockaddr));
+               if (ioctl(skfd, SIOCGIFDSTADDR, &ifr) >= 0)
+                       ife->dstaddr = ifr.ifr_dstaddr;
+
+               strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+               memset(&ife->broadaddr, 0, sizeof(struct sockaddr));
+               if (ioctl(skfd, SIOCGIFBRDADDR, &ifr) >= 0)
+                       ife->broadaddr = ifr.ifr_broadaddr;
+
+               strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+               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 *pr_ether(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 in_ether(const char *bufp, struct sockaddr *sap);
+
+static const struct hwtype ether_hwtype = {
+       .name =         "ether",
+       .title =        "Ethernet",
+       .type =         ARPHRD_ETHER,
+       .alen =         ETH_ALEN,
+       .print =        pr_ether,
+       .input =        in_ether
+};
+
+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 in_ether(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
+
+static const struct hwtype *const hwtypes[] = {
+       &loop_hwtype,
+       &ether_hwtype,
+       &ppp_hwtype,
+       &unspec_hwtype,
+#if ENABLE_FEATURE_IPV6
+       &sit_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 *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 *get_hwntype(int type)
+{
+       const struct hwtype *const *hwp;
+
+       hwp = hwtypes;
+       while (*hwp != NULL) {
+               if ((*hwp)->type == type)
+                       return *hwp;
+               hwp++;
+       }
+       return NULL;
+}
+
+/* return 1 if address is all zeros */
+static int hw_null_address(const struct hwtype *hw, void *ap)
+{
+       unsigned int i;
+       unsigned char *address = (unsigned char *) ap;
+
+       for (i = 0; i < hw->alen; i++)
+               if (address[i])
+                       return 0;
+       return 1;
+}
+
+static const char TRext[] 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);
+}
+
+static void ife_print(struct interface *ptr)
+{
+       const struct aftype *ap;
+       const struct hwtype *hw;
+       int hf;
+       int can_compress = 0;
+
+#ifdef HAVE_AFINET6
+       FILE *f;
+       char addr6[40], devname[20];
+       struct sockaddr_in6 sap;
+       int plen, scope, dad_status, if_idx;
+       char addr6p[8][5];
+#endif
+
+       ap = get_afntype(ptr->addr.sa_family);
+       if (ap == NULL)
+               ap = get_afntype(0);
+
+       hf = ptr->type;
+
+       if (hf == ARPHRD_CSLIP || hf == ARPHRD_CSLIP6)
+               can_compress = 1;
+
+       hw = get_hwntype(hf);
+       if (hw == NULL)
+               hw = get_hwntype(-1);
+
+       printf("%-9.9s Link encap:%s  ", ptr->name, hw->title);
+       /* For some hardware types (eg Ash, ATM) we don't print the
+          hardware address if it's null.  */
+       if (hw->print != NULL && (!(hw_null_address(hw, ptr->hwaddr) &&
+                                                               hw->suppress_null_addr)))
+               printf("HWaddr %s  ", hw->print((unsigned char *)ptr->hwaddr));
+#ifdef IFF_PORTSEL
+       if (ptr->flags & IFF_PORTSEL) {
+               printf("Media:%s", if_port_text[ptr->map.port] /* [0] */);
+               if (ptr->flags & IFF_AUTOMEDIA)
+                       printf("(auto)");
+       }
+#endif
+       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));
+       }
+
+#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 */
+
+       f = fopen(_PATH_PROCNET_IFINET6, "r");
+       if (f != NULL) {
+               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);
+       }
+#endif
+
+       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;
+}
+
+int 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..bb409c5
--- /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 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 ATTRIBUTE_NORETURN ip_print_help(char ATTRIBUTE_UNUSED **argv)
+{
+       bb_show_usage();
+}
+
+static int ip_do(int (*ip_func)(char **argv), char **argv)
+{
+       argv = ip_parse_common_args(argv);
+       return ip_func(argv);
+}
+
+#if ENABLE_FEATURE_IP_ADDRESS
+int ipaddr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipaddr_main(int argc ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, char **argv)
+{
+    return ip_do(do_iptunnel, argv);
+}
+#endif
+
+
+int ip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ip_main(int argc ATTRIBUTE_UNUSED, 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..d7c968c
--- /dev/null
@@ -0,0 +1,194 @@
+/* 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 <getopt.h>
+#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;
+                       int x;
+
+                       hostinfo = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET);
+                       if (!hostinfo) {
+                               bb_herror_msg_and_die("cannot find hostname for %s", argv[0]);
+                       }
+                       for (x = 0; hostinfo->h_name[x]; x++) {
+                               hostinfo->h_name[x] = tolower(hostinfo->h_name[x]);
+                       }
+
+                       printf("HOSTNAME=%s\n", hostinfo->h_name);
+               }
+       }
+
+       return EXIT_SUCCESS;
+}
diff --git a/networking/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..370ed90
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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.
+ */
+
+/* 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
+);
diff --git a/networking/isrv_identd.c b/networking/isrv_identd.c
new file mode 100644 (file)
index 0000000..d60c9fb
--- /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 <= 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 ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, 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, 0, 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..c047356
--- /dev/null
@@ -0,0 +1,32 @@
+/* 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
+
+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) ATTRIBUTE_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);
+#endif /* ip_common.h */
diff --git a/networking/libiproute/ip_parse_common_args.c b/networking/libiproute/ip_parse_common_args.c
new file mode 100644 (file)
index 0000000..294bde5
--- /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_strings(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..044538a
--- /dev/null
@@ -0,0 +1,786 @@
+/* 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 <sys/socket.h>
+//#include <sys/ioctl.h>
+#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"
+
+
+typedef struct filter_t {
+       int ifindex;
+       int family;
+       int oneline;
+       int showqueue;
+       inet_prefix pfx;
+       int scope, scopemask;
+       int flags, flagmask;
+       int up;
+       char *label;
+       int flushed;
+       char *flushb;
+       int flushp;
+       int flushe;
+       struct rtnl_handle *rth;
+} filter_t;
+
+#define filter (*(filter_t*)&bb_common_bufsiz1)
+
+
+static void print_link_flags(FILE *fp, unsigned flags, unsigned mdown)
+{
+       fprintf(fp, "<");
+       flags &= ~IFF_RUNNING;
+#define _PF(f) if (flags&IFF_##f) { \
+                 flags &= ~IFF_##f; \
+                 fprintf(fp, #f "%s", flags ? "," : ""); }
+       _PF(LOOPBACK);
+       _PF(BROADCAST);
+       _PF(POINTOPOINT);
+       _PF(MULTICAST);
+       _PF(NOARP);
+#if 0
+       _PF(ALLMULTI);
+       _PF(PROMISC);
+       _PF(MASTER);
+       _PF(SLAVE);
+       _PF(DEBUG);
+       _PF(DYNAMIC);
+       _PF(AUTOMEDIA);
+       _PF(PORTSEL);
+       _PF(NOTRAILERS);
+#endif
+       _PF(UP);
+#undef _PF
+       if (flags)
+               fprintf(fp, "%x", flags);
+       if (mdown)
+               fprintf(fp, ",M-DOWN");
+       fprintf(fp, "> ");
+}
+
+static void print_queuelen(char *name)
+{
+       struct ifreq ifr;
+       int s;
+
+       s = socket(AF_INET, SOCK_STREAM, 0);
+       if (s < 0)
+               return;
+
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+       if (ioctl_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(struct sockaddr_nl ATTRIBUTE_UNUSED *who,
+               const struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg)
+{
+       FILE *fp = (FILE*)arg;
+       struct ifinfomsg *ifi = NLMSG_DATA(n);
+       struct rtattr * tb[IFLA_MAX+1];
+       int len = n->nlmsg_len;
+       unsigned m_flag = 0;
+
+       if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+               return 0;
+
+       len -= NLMSG_LENGTH(sizeof(*ifi));
+       if (len < 0)
+               return -1;
+
+       if (filter.ifindex && ifi->ifi_index != filter.ifindex)
+               return 0;
+       if (filter.up && !(ifi->ifi_flags&IFF_UP))
+               return 0;
+
+       memset(tb, 0, sizeof(tb));
+       parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+       if (tb[IFLA_IFNAME] == NULL) {
+               bb_error_msg("nil ifname");
+               return -1;
+       }
+       if (filter.label
+        && (!filter.family || filter.family == AF_PACKET)
+        && fnmatch(filter.label, RTA_DATA(tb[IFLA_IFNAME]), 0)
+       ) {
+               return 0;
+       }
+
+       if (n->nlmsg_type == RTM_DELLINK)
+               fprintf(fp, "Deleted ");
+
+       fprintf(fp, "%d: %s", ifi->ifi_index,
+               tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : "<nil>");
+
+       if (tb[IFLA_LINK]) {
+               SPRINT_BUF(b1);
+               int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]);
+               if (iflink == 0)
+                       fprintf(fp, "@NONE: ");
+               else {
+                       fprintf(fp, "@%s: ", ll_idx_n2a(iflink, b1));
+                       m_flag = ll_index_to_flags(iflink);
+                       m_flag = !(m_flag & IFF_UP);
+               }
+       } else {
+               fprintf(fp, ": ");
+       }
+       print_link_flags(fp, ifi->ifi_flags, m_flag);
+
+       if (tb[IFLA_MTU])
+               fprintf(fp, "mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU]));
+       if (tb[IFLA_QDISC])
+               fprintf(fp, "qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC]));
+#ifdef IFLA_MASTER
+       if (tb[IFLA_MASTER]) {
+               SPRINT_BUF(b1);
+               fprintf(fp, "master %s ", ll_idx_n2a(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1));
+       }
+#endif
+       if (filter.showqueue)
+               print_queuelen((char*)RTA_DATA(tb[IFLA_IFNAME]));
+
+       if (!filter.family || filter.family == AF_PACKET) {
+               SPRINT_BUF(b1);
+               fprintf(fp, "%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)), fp);
+               }
+               if (tb[IFLA_BROADCAST]) {
+                       if (ifi->ifi_flags & IFF_POINTOPOINT)
+                               fprintf(fp, " peer ");
+                       else
+                               fprintf(fp, " brd ");
+                       fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]),
+                                                     RTA_PAYLOAD(tb[IFLA_BROADCAST]),
+                                                     ifi->ifi_type,
+                                                     b1, sizeof(b1)), fp);
+               }
+       }
+       fputc('\n', fp);
+       fflush(fp);
+       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(struct sockaddr_nl ATTRIBUTE_UNUSED *who,
+               struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg)
+{
+       FILE *fp = (FILE*)arg;
+       struct ifaddrmsg *ifa = NLMSG_DATA(n);
+       int len = n->nlmsg_len;
+       struct rtattr * rta_tb[IFA_MAX+1];
+       char abuf[256];
+       SPRINT_BUF(b1);
+
+       if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR)
+               return 0;
+       len -= NLMSG_LENGTH(sizeof(*ifa));
+       if (len < 0) {
+               bb_error_msg("wrong nlmsg len %d", len);
+               return -1;
+       }
+
+       if (filter.flushb && n->nlmsg_type != RTM_NEWADDR)
+               return 0;
+
+       memset(rta_tb, 0, sizeof(rta_tb));
+       parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+
+       if (!rta_tb[IFA_LOCAL])
+               rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
+       if (!rta_tb[IFA_ADDRESS])
+               rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
+
+       if (filter.ifindex && filter.ifindex != ifa->ifa_index)
+               return 0;
+       if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
+               return 0;
+       if ((filter.flags^ifa->ifa_flags)&filter.flagmask)
+               return 0;
+       if (filter.label) {
+               const char *label;
+               if (rta_tb[IFA_LABEL])
+                       label = RTA_DATA(rta_tb[IFA_LABEL]);
+               else
+                       label = ll_idx_n2a(ifa->ifa_index, b1);
+               if (fnmatch(filter.label, label, 0) != 0)
+                       return 0;
+       }
+       if (filter.pfx.family) {
+               if (rta_tb[IFA_LOCAL]) {
+                       inet_prefix dst;
+                       memset(&dst, 0, sizeof(dst));
+                       dst.family = ifa->ifa_family;
+                       memcpy(&dst.data, RTA_DATA(rta_tb[IFA_LOCAL]), RTA_PAYLOAD(rta_tb[IFA_LOCAL]));
+                       if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen))
+                               return 0;
+               }
+       }
+
+       if (filter.flushb) {
+               struct nlmsghdr *fn;
+               if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+                       if (flush_update())
+                               return -1;
+               }
+               fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+               memcpy(fn, n, n->nlmsg_len);
+               fn->nlmsg_type = RTM_DELADDR;
+               fn->nlmsg_flags = NLM_F_REQUEST;
+               fn->nlmsg_seq = ++filter.rth->seq;
+               filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb;
+               filter.flushed++;
+               return 0;
+       }
+
+       if (n->nlmsg_type == RTM_DELADDR)
+               fprintf(fp, "Deleted ");
+
+       if (filter.oneline)
+               fprintf(fp, "%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index));
+       if (ifa->ifa_family == AF_INET)
+               fprintf(fp, "    inet ");
+       else if (ifa->ifa_family == AF_INET6)
+               fprintf(fp, "    inet6 ");
+       else
+               fprintf(fp, "    family %d ", ifa->ifa_family);
+
+       if (rta_tb[IFA_LOCAL]) {
+               fputs(rt_addr_n2a(ifa->ifa_family,
+                                             RTA_PAYLOAD(rta_tb[IFA_LOCAL]),
+                                             RTA_DATA(rta_tb[IFA_LOCAL]),
+                                             abuf, sizeof(abuf)), fp);
+
+               if (rta_tb[IFA_ADDRESS] == NULL ||
+                   memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0) {
+                       fprintf(fp, "/%d ", ifa->ifa_prefixlen);
+               } else {
+                       fprintf(fp, " peer %s/%d ",
+                               rt_addr_n2a(ifa->ifa_family,
+                                           RTA_PAYLOAD(rta_tb[IFA_ADDRESS]),
+                                           RTA_DATA(rta_tb[IFA_ADDRESS]),
+                                           abuf, sizeof(abuf)),
+                               ifa->ifa_prefixlen);
+               }
+       }
+
+       if (rta_tb[IFA_BROADCAST]) {
+               fprintf(fp, "brd %s ",
+                       rt_addr_n2a(ifa->ifa_family,
+                                   RTA_PAYLOAD(rta_tb[IFA_BROADCAST]),
+                                   RTA_DATA(rta_tb[IFA_BROADCAST]),
+                                   abuf, sizeof(abuf)));
+       }
+       if (rta_tb[IFA_ANYCAST]) {
+               fprintf(fp, "any %s ",
+                       rt_addr_n2a(ifa->ifa_family,
+                                   RTA_PAYLOAD(rta_tb[IFA_ANYCAST]),
+                                   RTA_DATA(rta_tb[IFA_ANYCAST]),
+                                   abuf, sizeof(abuf)));
+       }
+       fprintf(fp, "scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1)));
+       if (ifa->ifa_flags&IFA_F_SECONDARY) {
+               ifa->ifa_flags &= ~IFA_F_SECONDARY;
+               fprintf(fp, "secondary ");
+       }
+       if (ifa->ifa_flags&IFA_F_TENTATIVE) {
+               ifa->ifa_flags &= ~IFA_F_TENTATIVE;
+               fprintf(fp, "tentative ");
+       }
+       if (ifa->ifa_flags&IFA_F_DEPRECATED) {
+               ifa->ifa_flags &= ~IFA_F_DEPRECATED;
+               fprintf(fp, "deprecated ");
+       }
+       if (!(ifa->ifa_flags&IFA_F_PERMANENT)) {
+               fprintf(fp, "dynamic ");
+       } else
+               ifa->ifa_flags &= ~IFA_F_PERMANENT;
+       if (ifa->ifa_flags)
+               fprintf(fp, "flags %02x ", ifa->ifa_flags);
+       if (rta_tb[IFA_LABEL])
+               fputs((char*)RTA_DATA(rta_tb[IFA_LABEL]), fp);
+       if (rta_tb[IFA_CACHEINFO]) {
+               struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]);
+               char buf[128];
+               fputc(_SL_, fp);
+               if (ci->ifa_valid == 0xFFFFFFFFU)
+                       sprintf(buf, "valid_lft forever");
+               else
+                       sprintf(buf, "valid_lft %dsec", ci->ifa_valid);
+               if (ci->ifa_prefered == 0xFFFFFFFFU)
+                       sprintf(buf+strlen(buf), " preferred_lft forever");
+               else
+                       sprintf(buf+strlen(buf), " preferred_lft %dsec", ci->ifa_prefered);
+               fprintf(fp, "       %s", buf);
+       }
+       fputc('\n', fp);
+       fflush(fp);
+       return 0;
+}
+
+
+struct nlmsg_list
+{
+       struct nlmsg_list *next;
+       struct nlmsghdr   h;
+};
+
+static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo, FILE *fp)
+{
+       for (; ainfo; ainfo = ainfo->next) {
+               struct nlmsghdr *n = &ainfo->h;
+               struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+               if (n->nlmsg_type != RTM_NEWADDR)
+                       continue;
+
+               if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifa)))
+                       return -1;
+
+               if (ifa->ifa_index != ifindex ||
+                   (filter.family && filter.family != ifa->ifa_family))
+                       continue;
+
+               print_addrinfo(NULL, n, fp);
+       }
+       return 0;
+}
+
+
+static int store_nlmsg(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+       struct nlmsg_list **linfo = (struct nlmsg_list**)arg;
+       struct nlmsg_list *h;
+       struct nlmsg_list **lp;
+
+       h = malloc(n->nlmsg_len+sizeof(void*));
+       if (h == NULL)
+               return -1;
+
+       memcpy(&h->h, n, n->nlmsg_len);
+       h->next = NULL;
+
+       for (lp = linfo; *lp; lp = &(*lp)->next) /* NOTHING */;
+       *lp = h;
+
+       ll_remember_index(who, n, NULL);
+       return 0;
+}
+
+static void ipaddr_reset_filter(int _oneline)
+{
+       memset(&filter, 0, sizeof(filter));
+       filter.oneline = _oneline;
+}
+
+/* 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, stdout);
+                       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(NULL, &l->h, stdout) == 0) {
+                       struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+                       if (filter.family != AF_PACKET)
+                               print_selected_addrinfo(ifi->ifi_index, ainfo, stdout);
+               }
+       }
+
+       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..494b223
--- /dev/null
@@ -0,0 +1,301 @@
+/* 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(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+       strncpy(ifr.ifr_newname, newdev, sizeof(ifr.ifr_newname));
+       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(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+       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(ifr->ifr_name, dev, sizeof(ifr->ifr_name));
+       ifr->ifr_hwaddr.sa_family = hatype;
+       alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), 14, lla);
+       if (alen < 0)
+               exit(1);
+       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) ATTRIBUTE_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""addr\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) {
+               key = index_in_strings(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);
+                       if (get_integer(&mtu, *argv, 0))
+                               invarg(*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..670f188
--- /dev/null
@@ -0,0 +1,889 @@
+/* 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;
+       int flushed;
+       char *flushb;
+       int flushp;
+       int flushe;
+       struct rtnl_handle *rth;
+       int protocol, protocolmask;
+       int scope, scopemask;
+       int type, typemask;
+       int tos, tosmask;
+       int iif, iifmask;
+       int oif, oifmask;
+       int realm, realmmask;
+       inet_prefix rprefsrc;
+       inet_prefix rvia;
+       inet_prefix rdst;
+       inet_prefix mdst;
+       inet_prefix rsrc;
+       inet_prefix msrc;
+} filter_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("/proc/net/psched", "r");
+       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(struct sockaddr_nl *who ATTRIBUTE_UNUSED,
+               struct nlmsghdr *n, void *arg)
+{
+       FILE *fp = (FILE*)arg;
+       struct rtmsg *r = NLMSG_DATA(n);
+       int len = n->nlmsg_len;
+       struct rtattr * tb[RTA_MAX+1];
+       char abuf[256];
+       inet_prefix dst;
+       inet_prefix src;
+       int host_len = -1;
+       SPRINT_BUF(b1);
+
+       if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) {
+               fprintf(stderr, "Not a route: %08x %08x %08x\n",
+                       n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+               return 0;
+       }
+       if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE)
+               return 0;
+       len -= NLMSG_LENGTH(sizeof(*r));
+       if (len < 0)
+               bb_error_msg_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++;
+               return 0;
+       }
+
+       if (n->nlmsg_type == RTM_DELROUTE) {
+               fprintf(fp, "Deleted ");
+       }
+       if (r->rtm_type != RTN_UNICAST && !filter.type) {
+               fprintf(fp, "%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)));
+       }
+
+       if (tb[RTA_DST]) {
+               if (r->rtm_dst_len != host_len) {
+                       fprintf(fp, "%s/%u ", rt_addr_n2a(r->rtm_family,
+                                                        RTA_PAYLOAD(tb[RTA_DST]),
+                                                        RTA_DATA(tb[RTA_DST]),
+                                                        abuf, sizeof(abuf)),
+                               r->rtm_dst_len
+                               );
+               } else {
+                       fprintf(fp, "%s ", format_host(r->rtm_family,
+                                                      RTA_PAYLOAD(tb[RTA_DST]),
+                                                      RTA_DATA(tb[RTA_DST]),
+                                                      abuf, sizeof(abuf))
+                               );
+               }
+       } else if (r->rtm_dst_len) {
+               fprintf(fp, "0/%d ", r->rtm_dst_len);
+       } else {
+               fprintf(fp, "default ");
+       }
+       if (tb[RTA_SRC]) {
+               if (r->rtm_src_len != host_len) {
+                       fprintf(fp, "from %s/%u ", rt_addr_n2a(r->rtm_family,
+                                                        RTA_PAYLOAD(tb[RTA_SRC]),
+                                                        RTA_DATA(tb[RTA_SRC]),
+                                                        abuf, sizeof(abuf)),
+                               r->rtm_src_len
+                               );
+               } else {
+                       fprintf(fp, "from %s ", format_host(r->rtm_family,
+                                                      RTA_PAYLOAD(tb[RTA_SRC]),
+                                                      RTA_DATA(tb[RTA_SRC]),
+                                                      abuf, sizeof(abuf))
+                               );
+               }
+       } else if (r->rtm_src_len) {
+               fprintf(fp, "from 0/%u ", r->rtm_src_len);
+       }
+       if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) {
+               fprintf(fp, "via %s ",
+                       format_host(r->rtm_family,
+                                   RTA_PAYLOAD(tb[RTA_GATEWAY]),
+                                   RTA_DATA(tb[RTA_GATEWAY]),
+                                   abuf, sizeof(abuf)));
+       }
+       if (tb[RTA_OIF] && filter.oifmask != -1) {
+               fprintf(fp, "dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF])));
+       }
+
+       if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) {
+               /* Do not use format_host(). It is our local addr
+                  and symbolic name will not be useful.
+                */
+               fprintf(fp, " src %s ",
+                       rt_addr_n2a(r->rtm_family,
+                                   RTA_PAYLOAD(tb[RTA_PREFSRC]),
+                                   RTA_DATA(tb[RTA_PREFSRC]),
+                                   abuf, sizeof(abuf)));
+       }
+       if (tb[RTA_PRIORITY]) {
+               fprintf(fp, " metric %d ", *(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) {
+                               fprintf(fp, "%c    cache ", _SL_);
+                       }
+                       if (ci->rta_expires) {
+                               fprintf(fp, " expires %dsec", ci->rta_expires / get_hz());
+                       }
+                       if (ci->rta_error != 0) {
+                               fprintf(fp, " error %d", ci->rta_error);
+                       }
+               } else if (ci) {
+                       if (ci->rta_error != 0)
+                               fprintf(fp, " error %d", ci->rta_error);
+               }
+       }
+       if (tb[RTA_IIF] && filter.iifmask != -1) {
+               fprintf(fp, " iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF])));
+       }
+       fputc('\n', fp);
+       fflush(fp);
+       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";
+       enum {
+               ARG_src,
+               ARG_via,
+               ARG_mtu, PARM_lock,
+               ARG_protocol,
+USE_FEATURE_IP_RULE(ARG_table,)
+               ARG_dev,
+               ARG_oif,
+               ARG_to
+       };
+       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;
+       req.r.rtm_table = RT_TABLE_MAIN;
+       req.r.rtm_scope = RT_SCOPE_NOWHERE;
+
+       if (cmd != RTM_DELROUTE) {
+               req.r.rtm_protocol = RTPROT_BOOT;
+               req.r.rtm_scope = RT_SCOPE_UNIVERSE;
+               req.r.rtm_type = RTN_UNICAST;
+       }
+
+       mxrta->rta_type = RTA_METRICS;
+       mxrta->rta_len = RTA_LENGTH(0);
+
+       while (*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();
+                       }
+                       if (get_unsigned(&mtu, *argv, 0))
+                               invarg(*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 {
+                       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);
+       req.nlh.nlmsg_type = RTM_GETROUTE;
+       req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_REQUEST;
+       req.nlh.nlmsg_pid = 0;
+       req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
+       req.rtm.rtm_family = family;
+       req.rtm.rtm_flags |= RTM_F_CLONED;
+
+       return 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)
+                               invarg(*argv, "table");
+               } 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, stdout);
+                       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, stdout);
+
+       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));
+       req.n.nlmsg_flags = NLM_F_REQUEST;
+       req.n.nlmsg_type = RTM_GETROUTE;
+       req.r.rtm_family = preferred_family;
+       req.r.rtm_table = 0;
+       req.r.rtm_protocol = 0;
+       req.r.rtm_scope = 0;
+       req.r.rtm_type = 0;
+       req.r.rtm_src_len = 0;
+       req.r.rtm_dst_len = 0;
+       req.r.rtm_tos = 0;
+
+       while (*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, (void*)stdout);
+
+               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, (void*)stdout);
+       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..3f9007e
--- /dev/null
@@ -0,0 +1,335 @@
+/* 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 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(struct sockaddr_nl *who ATTRIBUTE_UNUSED,
+                                       struct nlmsghdr *n, void *arg)
+{
+       FILE *fp = (FILE*)arg;
+       struct rtmsg *r = NLMSG_DATA(n);
+       int len = n->nlmsg_len;
+       int host_len = -1;
+       struct rtattr * tb[RTA_MAX+1];
+       char abuf[256];
+       SPRINT_BUF(b1);
+
+       if (n->nlmsg_type != RTM_NEWRULE)
+               return 0;
+
+       len -= NLMSG_LENGTH(sizeof(*r));
+       if (len < 0)
+               return -1;
+
+       memset(tb, 0, sizeof(tb));
+       parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+       if (r->rtm_family == AF_INET)
+               host_len = 32;
+       else if (r->rtm_family == AF_INET6)
+               host_len = 128;
+/*     else if (r->rtm_family == AF_DECnet)
+               host_len = 16;
+       else if (r->rtm_family == AF_IPX)
+               host_len = 80;
+*/
+       if (tb[RTA_PRIORITY])
+               fprintf(fp, "%u:\t", *(unsigned*)RTA_DATA(tb[RTA_PRIORITY]));
+       else
+               fprintf(fp, "0:\t");
+
+       fprintf(fp, "from ");
+       if (tb[RTA_SRC]) {
+               if (r->rtm_src_len != host_len) {
+                       fprintf(fp, "%s/%u", rt_addr_n2a(r->rtm_family,
+                                                        RTA_PAYLOAD(tb[RTA_SRC]),
+                                                        RTA_DATA(tb[RTA_SRC]),
+                                                        abuf, sizeof(abuf)),
+                               r->rtm_src_len
+                               );
+               } else {
+                       fputs(format_host(r->rtm_family,
+                                                      RTA_PAYLOAD(tb[RTA_SRC]),
+                                                      RTA_DATA(tb[RTA_SRC]),
+                                                      abuf, sizeof(abuf)), fp);
+               }
+       } else if (r->rtm_src_len) {
+               fprintf(fp, "0/%d", r->rtm_src_len);
+       } else {
+               fprintf(fp, "all");
+       }
+       fprintf(fp, " ");
+
+       if (tb[RTA_DST]) {
+               if (r->rtm_dst_len != host_len) {
+                       fprintf(fp, "to %s/%u ", rt_addr_n2a(r->rtm_family,
+                                                        RTA_PAYLOAD(tb[RTA_DST]),
+                                                        RTA_DATA(tb[RTA_DST]),
+                                                        abuf, sizeof(abuf)),
+                               r->rtm_dst_len
+                               );
+               } else {
+                       fprintf(fp, "to %s ", format_host(r->rtm_family,
+                                                      RTA_PAYLOAD(tb[RTA_DST]),
+                                                      RTA_DATA(tb[RTA_DST]),
+                                                      abuf, sizeof(abuf)));
+               }
+       } else if (r->rtm_dst_len) {
+               fprintf(fp, "to 0/%d ", r->rtm_dst_len);
+       }
+
+       if (r->rtm_tos) {
+               fprintf(fp, "tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1)));
+       }
+       if (tb[RTA_PROTOINFO]) {
+               fprintf(fp, "fwmark %#x ", *(uint32_t*)RTA_DATA(tb[RTA_PROTOINFO]));
+       }
+
+       if (tb[RTA_IIF]) {
+               fprintf(fp, "iif %s ", (char*)RTA_DATA(tb[RTA_IIF]));
+       }
+
+       if (r->rtm_table)
+               fprintf(fp, "lookup %s ", rtnl_rttable_n2a(r->rtm_table, b1, sizeof(b1)));
+
+       if (tb[RTA_FLOW]) {
+               uint32_t to = *(uint32_t*)RTA_DATA(tb[RTA_FLOW]);
+               uint32_t from = to>>16;
+               to &= 0xFFFF;
+               if (from) {
+                       fprintf(fp, "realms %s/",
+                               rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+               }
+               fprintf(fp, "%s ",
+                       rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+       }
+
+       if (r->rtm_type == RTN_NAT) {
+               if (tb[RTA_GATEWAY]) {
+                       fprintf(fp, "map-to %s ",
+                               format_host(r->rtm_family,
+                                           RTA_PAYLOAD(tb[RTA_GATEWAY]),
+                                           RTA_DATA(tb[RTA_GATEWAY]),
+                                           abuf, sizeof(abuf)));
+               } else
+                       fprintf(fp, "masquerade");
+       } else if (r->rtm_type != RTN_UNICAST)
+               fputs(rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)), fp);
+
+       fputc('\n', fp);
+       fflush(fp);
+       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, stdout);
+
+       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();
+                       if (get_u32(&pref, *argv, 0))
+                               invarg(*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();
+                       if (get_u32(&fwmark, *argv, 0))
+                               invarg(*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..ad909ff
--- /dev/null
@@ -0,0 +1,539 @@
+/* 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
+#include <linux/if_tunnel.h>
+
+#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(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, basedev, sizeof(ifr.ifr_name));
+       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(ifr.ifr_name, p->name, sizeof(ifr.ifr_name));
+       } else {
+               strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name));
+       }
+       ifr.ifr_ifru.ifru_data = (void*)p;
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+#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(ifr.ifr_name, p->name, sizeof(ifr.ifr_name));
+       } else {
+               strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name));
+       }
+       ifr.ifr_ifru.ifru_data = (void*)p;
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+       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));
+       memset(&medium, 0, sizeof(medium));
+
+       p->iph.version = 4;
+       p->iph.ihl = 5;
+#ifndef IP_DF
+#define IP_DF 0x4000  /* Flag: "Don't Fragment" */
+#endif
+       p->iph.frag_off = htons(IP_DF);
+
+       while (*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("you managed to ask for more than one tunnel mode");
+                               }
+                               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("you managed to ask for more than one tunnel mode");
+                               }
+                               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("you managed to ask for more than one tunnel mode");
+                               }
+                               p->iph.protocol = IPPROTO_IPV6;
+                       } else {
+                               bb_error_msg_and_die("cannot guess tunnel mode");
+                       }
+               } 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 {
+                               if (get_unsigned(&uval, *argv, 0)<0) {
+                                       bb_error_msg_and_die("invalid value of \"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 {
+                               if (get_unsigned(&uval, *argv, 0)<0) {
+                                       bb_error_msg_and_die("invalid value of \"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 {
+                               if (get_unsigned(&uval, *argv, 0)<0) {
+                                       bb_error_msg_and_die("invalid value of \"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(medium, *argv, IFNAMSIZ-1);
+               } else if (key == ARG_ttl) {
+                       unsigned uval;
+                       NEXT_ARG();
+                       key = index_in_strings(keywords, *argv);
+                       if (key != ARG_inherit) {
+                               if (get_unsigned(&uval, *argv, 0))
+                                       invarg(*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(p->name, *argv, IFNAMSIZ);
+                       if (cmd == SIOCCHGTUNNEL && count == 0) {
+                               struct ip_tunnel_parm old_p;
+                               memset(&old_p, 0, sizeof(old_p));
+                               if (do_get_ioctl(*argv, &old_p))
+                                       exit(1);
+                               *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;
+       }
+
+       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..d29d035
--- /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 rtnl_close(struct rtnl_handle *rth)
+{
+       close(rth->fd);
+}
+
+int 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("cannot 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 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 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 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)(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 xrtnl_dump_filter(struct rtnl_handle *rth,
+               int (*filter)(struct sockaddr_nl *, struct nlmsghdr *n, 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 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)
+{
+/* 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 >= 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 < 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 addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data)
+{
+       int len = RTA_LENGTH(4);
+       struct rtattr *rta;
+       if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen)
+               return -1;
+       rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
+       rta->rta_type = type;
+       rta->rta_len = len;
+       memcpy(RTA_DATA(rta), &data, 4);
+       n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
+       return 0;
+}
+
+int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
+{
+       int len = RTA_LENGTH(alen);
+       struct rtattr *rta;
+
+       if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen)
+               return -1;
+       rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
+       rta->rta_type = type;
+       rta->rta_len = len;
+       memcpy(RTA_DATA(rta), data, alen);
+       n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
+       return 0;
+}
+
+int rta_addattr32(struct rtattr *rta, int maxlen, int type, 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;
+       memcpy(RTA_DATA(subrta), &data, 4);
+       rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
+       return 0;
+}
+
+int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen)
+{
+       struct rtattr *subrta;
+       int len = RTA_LENGTH(alen);
+
+       if (RTA_ALIGN(rta->rta_len) + len > maxlen) {
+               return -1;
+       }
+       subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len));
+       subrta->rta_type = type;
+       subrta->rta_len = len;
+       memcpy(RTA_DATA(subrta), data, alen);
+       rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
+       return 0;
+}
+
+
+int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+       while (RTA_OK(rta, len)) {
+               if (rta->rta_type <= max) {
+                       tb[rta->rta_type] = rta;
+               }
+               rta = RTA_NEXT(rta,len);
+       }
+       if (len) {
+               bb_error_msg("deficit %d, rta_len=%d!", len, rta->rta_len);
+       }
+       return 0;
+}
diff --git a/networking/libiproute/libnetlink.h b/networking/libiproute/libnetlink.h
new file mode 100644 (file)
index 0000000..e149f52
--- /dev/null
@@ -0,0 +1,46 @@
+/* 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>
+
+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);
+extern void rtnl_close(struct rtnl_handle *rth);
+extern int xrtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type);
+extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len);
+extern int xrtnl_dump_filter(struct rtnl_handle *rth,
+                       int (*filter)(struct sockaddr_nl*, struct nlmsghdr *n, void*),
+                       void *arg1);
+
+/* 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);
+
+extern int rtnl_send(struct rtnl_handle *rth, char *buf, int);
+
+
+extern int addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data);
+extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen);
+extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, uint32_t data);
+extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen);
+
+extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len);
+
+#endif /* __LIBNETLINK_H__ */
diff --git a/networking/libiproute/ll_addr.c b/networking/libiproute/ll_addr.c
new file mode 100644 (file)
index 0000000..ab5a2c5
--- /dev/null
@@ -0,0 +1,83 @@
+/* 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)
+{
+       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;
+       } else {
+               int i;
+
+               for (i=0; i<len; i++) {
+                       int temp;
+                       char *cp = strchr(arg, ':');
+                       if (cp) {
+                               *cp = 0;
+                               cp++;
+                       }
+                       if (sscanf(arg, "%x", &temp) != 1) {
+                               bb_error_msg("\"%s\" is invalid lladdr", arg);
+                               return -1;
+                       }
+                       if (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..3cfc9cc
--- /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(struct sockaddr_nl *who ATTRIBUTE_UNUSED,
+               struct nlmsghdr *n,
+               void *arg ATTRIBUTE_UNUSED)
+{
+       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 > 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) {
+               struct ifreq ifr;
+               int tmp;
+
+               strncpy(ifr.ifr_name, name, IFNAMSIZ);
+               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..55e2cf3
--- /dev/null
@@ -0,0 +1,13 @@
+/* vi: set sw=4 ts=4: */
+#ifndef __LL_MAP_H__
+#define __LL_MAP_H__ 1
+
+int ll_remember_index(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);
+
+#endif /* __LL_MAP_H__ */
diff --git a/networking/libiproute/ll_proto.c b/networking/libiproute/ll_proto.c
new file mode 100644 (file)
index 0000000..62262c9
--- /dev/null
@@ -0,0 +1,128 @@
+/* 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
+
+#ifdef UNUSED
+/* 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)
+{
+       int 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)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(llproto_names); i++) {
+                if (strcasecmp(llproto_names[i].name, buf) == 0) {
+                        *id = htons(llproto_names[i].id);
+                        return 0;
+                }
+       }
+       if (get_u16(id, buf, 0))
+               return -1;
+       *id = htons(*id);
+       return 0;
+}
+
+#endif /* UNUSED */
diff --git a/networking/libiproute/ll_types.c b/networking/libiproute/ll_types.c
new file mode 100644 (file)
index 0000000..60a78c7
--- /dev/null
@@ -0,0 +1,199 @@
+/* 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_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_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 */
+       };
+
+       int 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..797c83b
--- /dev/null
@@ -0,0 +1,365 @@
+/* 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"
+
+static void rtnl_tab_initialize(const char *file, const char **tab, int size)
+{
+       char buf[512];
+       FILE *fp;
+
+       fp = fopen(file, "r");
+       if (!fp)
+               return;
+       while (fgets(buf, sizeof(buf), fp)) {
+               char *p = buf;
+               int id;
+               char namebuf[512];
+
+               while (*p == ' ' || *p == '\t')
+                       p++;
+               if (*p == '#' || *p == '\n' || *p == 0)
+                       continue;
+               if (sscanf(p, "0x%x %s\n", &id, namebuf) != 2
+                && sscanf(p, "0x%x %s #", &id, namebuf) != 2
+                && sscanf(p, "%d %s\n", &id, namebuf) != 2
+                && sscanf(p, "%d %s #", &id, namebuf) != 2
+               ) {
+                       bb_error_msg("database %s is corrupted at %s",
+                               file, p);
+                       return;
+               }
+
+               if (id < 0 || id > size)
+                       continue;
+
+               tab[id] = xstrdup(namebuf);
+       }
+       fclose(fp);
+}
+
+
+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..f560f24
--- /dev/null
@@ -0,0 +1,30 @@
+/* vi: set sw=4 ts=4: */
+#ifndef RT_NAMES_H_
+#define RT_NAMES_H_ 1
+
+#include <stdint.h>
+
+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);
+
+#ifdef UNUSED
+extern const char* ll_proto_n2a(unsigned short id, char *buf, int len);
+extern int ll_proto_a2n(unsigned short *id, char *buf);
+#endif
+
+#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..cbbcc21
--- /dev/null
@@ -0,0 +1,11 @@
+/* vi: set sw=4 ts=4: */
+#ifndef __RTM_MAP_H__
+#define __RTM_MAP_H__ 1
+
+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);
+
+
+#endif /* __RTM_MAP_H__ */
diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c
new file mode 100644 (file)
index 0000000..e63bb27
--- /dev/null
@@ -0,0 +1,322 @@
+/* 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"
+
+int get_integer(int *val, char *arg, int base)
+{
+       long res;
+       char *ptr;
+
+       if (!arg || !*arg)
+               return -1;
+       res = strtol(arg, &ptr, base);
+       if (!ptr || ptr == arg || *ptr || res > INT_MAX || res < INT_MIN)
+               return -1;
+       *val = res;
+       return 0;
+}
+//XXX: FIXME: use some libbb function instead
+int get_unsigned(unsigned *val, char *arg, int base)
+{
+       unsigned long res;
+       char *ptr;
+
+       if (!arg || !*arg)
+               return -1;
+       res = strtoul(arg, &ptr, base);
+       if (!ptr || ptr == arg || *ptr || res > UINT_MAX)
+               return -1;
+       *val = res;
+       return 0;
+}
+
+int get_u32(uint32_t * val, char *arg, int base)
+{
+       unsigned long res;
+       char *ptr;
+
+       if (!arg || !*arg)
+               return -1;
+       res = strtoul(arg, &ptr, base);
+       if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL)
+               return -1;
+       *val = res;
+       return 0;
+}
+
+int get_u16(uint16_t * val, char *arg, int base)
+{
+       unsigned long res;
+       char *ptr;
+
+       if (!arg || !*arg)
+               return -1;
+       res = strtoul(arg, &ptr, base);
+       if (!ptr || ptr == arg || *ptr || res > 0xFFFF)
+               return -1;
+       *val = res;
+       return 0;
+}
+
+int get_u8(uint8_t * val, char *arg, int base)
+{
+       unsigned long res;
+       char *ptr;
+
+       if (!arg || !*arg)
+               return -1;
+       res = strtoul(arg, &ptr, base);
+       if (!ptr || ptr == arg || *ptr || res > 0xFF)
+               return -1;
+       *val = res;
+       return 0;
+}
+
+int get_s16(int16_t * val, char *arg, int base)
+{
+       long res;
+       char *ptr;
+
+       if (!arg || !*arg)
+               return -1;
+       res = strtol(arg, &ptr, base);
+       if (!ptr || ptr == arg || *ptr || res > 0x7FFF || res < -0x8000)
+               return -1;
+       *val = res;
+       return 0;
+}
+
+int get_s8(int8_t * val, char *arg, int base)
+{
+       long res;
+       char *ptr;
+
+       if (!arg || !*arg)
+               return -1;
+       res = strtol(arg, &ptr, base);
+       if (!ptr || ptr == arg || *ptr || res > 0x7F || res < -0x80)
+               return -1;
+       *val = res;
+       return 0;
+}
+
+int get_addr_1(inet_prefix * addr, char *name, int family)
+{
+       char *cp;
+       unsigned char *ap = (unsigned char *) addr->data;
+       int i;
+
+       memset(addr, 0, sizeof(*addr));
+
+       if (strcmp(name, bb_str_default) == 0 ||
+               strcmp(name, "all") == 0 || strcmp(name, "any") == 0) {
+               addr->family = family;
+               addr->bytelen = (family == AF_INET6 ? 16 : 4);
+               addr->bitlen = -1;
+               return 0;
+       }
+
+       if (strchr(name, ':')) {
+               addr->family = AF_INET6;
+               if (family != AF_UNSPEC && family != AF_INET6)
+                       return -1;
+               if (inet_pton(AF_INET6, name, addr->data) <= 0)
+                       return -1;
+               addr->bytelen = 16;
+               addr->bitlen = -1;
+               return 0;
+       }
+
+       addr->family = AF_INET;
+       if (family != AF_UNSPEC && family != AF_INET)
+               return -1;
+       addr->bytelen = 4;
+       addr->bitlen = -1;
+       for (cp = name, i = 0; *cp; cp++) {
+               if (*cp <= '9' && *cp >= '0') {
+                       ap[i] = 10 * ap[i] + (*cp - '0');
+                       continue;
+               }
+               if (*cp == '.' && ++i <= 3)
+                       continue;
+               return -1;
+       }
+       return 0;
+}
+
+int get_prefix_1(inet_prefix * dst, char *arg, int family)
+{
+       int err;
+       int plen;
+       char *slash;
+
+       memset(dst, 0, sizeof(*dst));
+
+       if (strcmp(arg, bb_str_default) == 0 || strcmp(arg, "any") == 0) {
+               dst->family = family;
+               dst->bytelen = 0;
+               dst->bitlen = 0;
+               return 0;
+       }
+
+       slash = strchr(arg, '/');
+       if (slash)
+               *slash = '\0';
+       err = get_addr_1(dst, arg, family);
+       if (err == 0) {
+               switch (dst->family) {
+               case AF_INET6:
+                       dst->bitlen = 128;
+                       break;
+               default:
+               case AF_INET:
+                       dst->bitlen = 32;
+               }
+               if (slash) {
+                       if (get_integer(&plen, slash + 1, 0) || plen > dst->bitlen) {
+                               err = -1;
+                               goto done;
+                       }
+                       dst->bitlen = plen;
+               }
+       }
+ done:
+       if (slash)
+               *slash = '/';
+       return err;
+}
+
+int get_addr(inet_prefix * dst, char *arg, int family)
+{
+       if (family == AF_PACKET) {
+               bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg);
+       }
+       if (get_addr_1(dst, arg, family)) {
+               bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg);
+       }
+       return 0;
+}
+
+int get_prefix(inet_prefix * dst, char *arg, int family)
+{
+       if (family == AF_PACKET) {
+               bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg);
+       }
+       if (get_prefix_1(dst, arg, family)) {
+               bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg);
+       }
+       return 0;
+}
+
+uint32_t get_addr32(char *name)
+{
+       inet_prefix addr;
+
+       if (get_addr_1(&addr, name, AF_INET)) {
+               bb_error_msg_and_die("an IP address is expected rather than \"%s\"", name);
+       }
+       return addr.data[0];
+}
+
+void incomplete_command(void)
+{
+       bb_error_msg_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 >> 0x05;
+
+       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, int ATTRIBUTE_UNUSED len,
+               void *addr, char *buf, int buflen)
+{
+       switch (af) {
+       case AF_INET:
+       case AF_INET6:
+               return inet_ntop(af, addr, buf, buflen);
+       default:
+               return "???";
+       }
+}
+
+
+const char *format_host(int af, int len, void *addr, char *buf, int buflen)
+{
+#ifdef RESOLVE_HOSTNAMES
+       if (resolve_hosts) {
+               struct hostent *h_ent;
+
+               if (len <= 0) {
+                       switch (af) {
+                       case AF_INET:
+                               len = 4;
+                               break;
+                       case AF_INET6:
+                               len = 16;
+                               break;
+                       default:;
+                       }
+               }
+               if (len > 0) {
+                       h_ent = gethostbyaddr(addr, len, af);
+                       if (h_ent != NULL) {
+                               safe_strncpy(buf, h_ent->h_name, buflen);
+                               return buf;
+                       }
+               }
+       }
+#endif
+       return rt_addr_n2a(af, len, addr, buf, buflen);
+}
diff --git a/networking/libiproute/utils.h b/networking/libiproute/utils.h
new file mode 100644 (file)
index 0000000..50a6c20
--- /dev/null
@@ -0,0 +1,87 @@
+/* vi: set sw=4 ts=4: */
+#ifndef __UTILS_H__
+#define __UTILS_H__ 1
+
+#include "libnetlink.h"
+#include "ll_map.h"
+#include "rtm_map.h"
+
+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) ATTRIBUTE_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 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 int get_integer(int *val, char *arg, int base);
+extern int get_unsigned(unsigned *val, char *arg, int base);
+#define get_byte get_u8
+#define get_ushort get_u16
+#define get_short get_s16
+extern int get_u32(uint32_t *val, char *arg, int base);
+extern int get_u16(uint16_t *val, char *arg, int base);
+extern int get_s16(int16_t *val, char *arg, int base);
+extern int get_u8(uint8_t *val, char *arg, int base);
+extern int get_s8(int8_t *val, char *arg, int base);
+
+extern const char *format_host(int af, int len, void *addr, char *buf, int buflen);
+extern const char *rt_addr_n2a(int af, int len, void *addr, char *buf, int buflen);
+
+void invarg(const char *, const char *) ATTRIBUTE_NORETURN;
+void duparg(const char *, const char *) ATTRIBUTE_NORETURN;
+void duparg2(const char *, const char *) ATTRIBUTE_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);
+
+#endif /* __UTILS_H__ */
diff --git a/networking/nameif.c b/networking/nameif.c
new file mode 100644 (file)
index 0000000..afc8891
--- /dev/null
@@ -0,0 +1,259 @@
+/* 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>
+
+/* Older versions of net/if.h do not appear to define IF_NAMESIZE. */
+#ifndef IF_NAMESIZE
+#  ifdef IFNAMSIZ
+#    define IF_NAMESIZE IFNAMSIZ
+#  else
+#    define IF_NAMESIZE 16
+#  endif
+#endif
+
+/* take from linux/sockios.h */
+#define SIOCSIFNAME    0x8923  /* set interface name */
+
+/* Octets in one Ethernet addr, from <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) >= IF_NAMESIZE)
+               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;
+       FILE *ifh;
+       const char *fname = "/etc/mactab";
+       char *line;
+       char *line_ptr;
+       int linenum;
+       int ctl_sk;
+       ethtable_t *ch;
+
+       if (1 & getopt32(argv, "sc:", &fname)) {
+               openlog(applet_name, 0, LOG_LOCAL0);
+               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 {
+               ifh = xfopen(fname, "r");
+               while ((line = xmalloc_fgets(ifh)) != NULL) {
+                       char *next;
+
+                       line_ptr = skip_whitespace(line);
+                       if ((line_ptr[0] == '#') || (line_ptr[0] == '\n'))
+                               goto read_next_line;
+                       next = skip_non_whitespace(line_ptr);
+                       if (*next)
+                               *next++ = '\0';
+                       prepend_new_eth_table(&clist, line_ptr, next);
+                       read_next_line:
+                       free(line);
+               }
+               fclose(ifh);
+       }
+
+       ctl_sk = xsocket(PF_INET, SOCK_DGRAM, 0);
+       ifh = xfopen("/proc/net/dev", "r");
+
+       linenum = 0;
+       while (clist) {
+               struct ifreq ifr;
+#if  ENABLE_FEATURE_NAMEIF_EXTENDED
+               struct ethtool_drvinfo drvinfo;
+#endif
+
+               line = xmalloc_fgets(ifh);
+               if (line == NULL)
+                       break; /* Seems like we're done */
+               if (linenum++ < 2 )
+                       goto next_line; /* Skip the first two lines */
+
+               /* Find the current interface name and copy it to ifr.ifr_name */
+               line_ptr = skip_whitespace(line);
+               *strpbrk(line_ptr, " \t\n:") = '\0';
+
+               memset(&ifr, 0, sizeof(struct ifreq));
+               strncpy(ifr.ifr_name, line_ptr, sizeof(ifr.ifr_name));
+
+#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 */
+                       goto found;
+               }
+               /* Nothing found for current interface */
+               goto next_line;
+ found:
+               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);
+ next_line:
+               free(line);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               for (ch = clist; ch; ch = ch->next)
+                       delete_eth_table(ch);
+               fclose(ifh);
+       };
+
+       return 0;
+}
diff --git a/networking/nc.c b/networking/nc.c
new file mode 100644 (file)
index 0000000..7d4a6e0
--- /dev/null
@@ -0,0 +1,203 @@
+/* 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 ATTRIBUTE_UNUSED)
+{
+       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);
+               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) */
+               if (cfd) {
+                       dup2(cfd, 0);
+                       close(cfd);
+               }
+               dup2(0, 1);
+               dup2(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(0);
+                                       ofd = STDOUT_FILENO;
+                               } else {
+                                       if (nread<1) {
+                                               // Close outgoing half-connection so they get EOF, but
+                                               // leave incoming alone so we can see response.
+                                               shutdown(cfd, 1);
+                                               FD_CLR(STDIN_FILENO, &readfds);
+                                       }
+                                       ofd = cfd;
+                               }
+                               xwrite(ofd, 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..dd94a14
--- /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 ATTRIBUTE_UNUSED)
+{
+       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) ATTRIBUTE_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];
+               int 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(&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(0, &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(0, &ding2)) {                /* stdin: ding! */
+                       rr = read(0, 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(0, &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(1, np, rnleft);
+                       if (rr > 0) {
+                               if (o_ofile)
+                                       oprint('<', np, rr);                /* log the stdout */
+                               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)
+                                       oprint('>', zp, rr);                /* log what got sent */
+                               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(0, &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..fd8d8ec
--- /dev/null
@@ -0,0 +1,573 @@
+/* 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>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "inet_common.h"
+
+enum {
+       OPT_extended = 0x4,
+       OPT_showroute = 0x100,
+       OPT_widedisplay = 0x200 * ENABLE_FEATURE_NETSTAT_WIDE,
+};
+# define NETSTAT_OPTS "laentuwxr"USE_FEATURE_NETSTAT_WIDE("W")
+
+#define NETSTAT_CONNECTED 0x01
+#define NETSTAT_LISTENING 0x02
+#define NETSTAT_NUMERIC   0x04
+/* Must match getopt32 option string */
+#define NETSTAT_TCP       0x10
+#define NETSTAT_UDP       0x20
+#define NETSTAT_RAW       0x40
+#define NETSTAT_UNIX      0x80
+#define NETSTAT_ALLPROTO  (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW|NETSTAT_UNIX)
+
+static smallint flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO;
+
+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\n"
+#define PRINT_NET_CONN_HEADER       "\nProto Recv-Q Send-Q %-23s %-23s State\n"
+
+/* 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\n"
+#define PRINT_NET_CONN_HEADER_WIDE  "\nProto Recv-Q Send-Q %-51s %-51s State\n"
+
+static const char *net_conn_line = PRINT_NET_CONN;
+
+
+#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)
+{
+       enum { salen = USE_FEATURE_IPV6(sizeof(struct sockaddr_in6)) SKIP_FEATURE_IPV6(sizeof(struct sockaddr_in)) };
+       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;
+}
+
+static int tcp_do_one(int lnr, char *line)
+{
+       char local_addr[64], rem_addr[64];
+       char more[512];
+       int num, local_port, rem_port, d, state, timer_run, uid, timeout;
+#if ENABLE_FEATURE_IPV6
+       struct sockaddr_in6 localaddr, remaddr;
+#else
+       struct sockaddr_in localaddr, remaddr;
+#endif
+       unsigned long rxq, txq, time_len, retr, inode;
+
+       if (lnr == 0)
+               return 0;
+
+       more[0] = '\0';
+       num = sscanf(line,
+                       "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n",
+                       &d, local_addr, &local_port,
+                       rem_addr, &rem_port, &state,
+                       &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more);
+
+       if (num < 10) {
+               return 1; /* error */
+       }
+
+       if (strlen(local_addr) > 8) {
+#if ENABLE_FEATURE_IPV6
+               build_ipv6_addr(local_addr, &localaddr);
+               build_ipv6_addr(rem_addr, &remaddr);
+#endif
+       } else {
+               build_ipv4_addr(local_addr, &localaddr);
+               build_ipv4_addr(rem_addr, &remaddr);
+       }
+
+       if ((rem_port && (flags & NETSTAT_CONNECTED))
+        || (!rem_port && (flags & NETSTAT_LISTENING))
+       ) {
+               char *l = ip_port_str(
+                               (struct sockaddr *) &localaddr, local_port,
+                               "tcp", flags & NETSTAT_NUMERIC);
+               char *r = ip_port_str(
+                               (struct sockaddr *) &remaddr, rem_port,
+                               "tcp", flags & NETSTAT_NUMERIC);
+               printf(net_conn_line,
+                       "tcp", rxq, txq, l, r, tcp_state[state]);
+               free(l);
+               free(r);
+       }
+       return 0;
+}
+
+static int udp_do_one(int lnr, char *line)
+{
+       char local_addr[64], rem_addr[64];
+       const char *state_str;
+       char more[512];
+       int num, local_port, rem_port, d, state, timer_run, uid, timeout;
+#if ENABLE_FEATURE_IPV6
+       struct sockaddr_in6 localaddr, remaddr;
+#else
+       struct sockaddr_in localaddr, remaddr;
+#endif
+       unsigned long rxq, txq, time_len, retr, inode;
+
+       if (lnr == 0)
+               return 0;
+
+       more[0] = '\0';
+       num = sscanf(line,
+                       "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n",
+                       &d, local_addr, &local_port,
+                       rem_addr, &rem_port, &state,
+                       &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more);
+
+       if (strlen(local_addr) > 8) {
+#if ENABLE_FEATURE_IPV6
+               /* Demangle what the kernel gives us */
+               build_ipv6_addr(local_addr, &localaddr);
+               build_ipv6_addr(rem_addr, &remaddr);
+#endif
+       } else {
+               build_ipv4_addr(local_addr, &localaddr);
+               build_ipv4_addr(rem_addr, &remaddr);
+       }
+
+       if (num < 10) {
+               return 1; /* error */
+       }
+       switch (state) {
+               case TCP_ESTABLISHED:
+                       state_str = "ESTABLISHED";
+                       break;
+               case TCP_CLOSE:
+                       state_str = "";
+                       break;
+               default:
+                       state_str = "UNKNOWN";
+                       break;
+       }
+
+#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
+       {
+               int have_remaddr = notnull(remaddr);
+               if ((have_remaddr && (flags & NETSTAT_CONNECTED))
+                || (!have_remaddr && (flags & NETSTAT_LISTENING))
+               ) {
+                       char *l = ip_port_str(
+                               (struct sockaddr *) &localaddr, local_port,
+                               "udp", flags & NETSTAT_NUMERIC);
+                       char *r = ip_port_str(
+                               (struct sockaddr *) &remaddr, rem_port,
+                               "udp", flags & NETSTAT_NUMERIC);
+                       printf(net_conn_line,
+                               "udp", rxq, txq, l, r, state_str);
+                       free(l);
+                       free(r);
+               }
+       }
+       return 0;
+}
+
+static int raw_do_one(int lnr, char *line)
+{
+       char local_addr[64], rem_addr[64];
+       char more[512];
+       int num, local_port, rem_port, d, state, timer_run, uid, timeout;
+#if ENABLE_FEATURE_IPV6
+       struct sockaddr_in6 localaddr, remaddr;
+#else
+       struct sockaddr_in localaddr, remaddr;
+#endif
+       unsigned long rxq, txq, time_len, retr, inode;
+
+       if (lnr == 0)
+               return 0;
+
+       more[0] = '\0';
+       num = sscanf(line,
+                       "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n",
+                       &d, local_addr, &local_port,
+                       rem_addr, &rem_port, &state,
+                       &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more);
+
+       if (strlen(local_addr) > 8) {
+#if ENABLE_FEATURE_IPV6
+               build_ipv6_addr(local_addr, &localaddr);
+               build_ipv6_addr(rem_addr, &remaddr);
+#endif
+       } else {
+               build_ipv4_addr(local_addr, &localaddr);
+               build_ipv4_addr(rem_addr, &remaddr);
+       }
+
+       if (num < 10) {
+               return 1; /* error */
+       }
+
+       {
+               int have_remaddr = notnull(remaddr);
+               if ((have_remaddr && (flags & NETSTAT_CONNECTED))
+                || (!have_remaddr && (flags & NETSTAT_LISTENING))
+               ) {
+                       char *l = ip_port_str(
+                               (struct sockaddr *) &localaddr, local_port,
+                               "raw", flags & NETSTAT_NUMERIC);
+                       char *r = ip_port_str(
+                               (struct sockaddr *) &remaddr, rem_port,
+                               "raw", flags & NETSTAT_NUMERIC);
+                       printf(net_conn_line,
+                               "raw", rxq, txq, l, r, itoa(state));
+                       free(l);
+                       free(r);
+               }
+       }
+       return 0;
+}
+
+static int unix_do_one(int nr, char *line)
+{
+       unsigned long refcnt, proto, unix_flags;
+       unsigned long inode;
+       int type, state;
+       int num, path_ofs;
+       void *d;
+       const char *ss_proto, *ss_state, *ss_type;
+       char ss_flags[32];
+
+       if (nr == 0)
+               return 0; /* skip header */
+
+       /* 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",
+                       &d, &refcnt, &proto, &unix_flags, &type, &state, &inode, &path_ofs);
+       if (num < 7) {
+               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
+               );
+
+       /* 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;
+}
+
+#define _PATH_PROCNET_UDP "/proc/net/udp"
+#define _PATH_PROCNET_UDP6 "/proc/net/udp6"
+#define _PATH_PROCNET_TCP "/proc/net/tcp"
+#define _PATH_PROCNET_TCP6 "/proc/net/tcp6"
+#define _PATH_PROCNET_RAW "/proc/net/raw"
+#define _PATH_PROCNET_RAW6 "/proc/net/raw6"
+#define _PATH_PROCNET_UNIX "/proc/net/unix"
+
+static void do_info(const char *file, const char *name, int (*proc)(int, char *))
+{
+       int lnr;
+       FILE *procinfo;
+       char *buffer;
+
+       procinfo = fopen(file, "r");
+       if (procinfo == NULL) {
+               if (errno != ENOENT) {
+                       bb_simple_perror_msg(file);
+               } else {
+                       bb_error_msg("no kernel support for %s", name);
+               }
+               return;
+       }
+       lnr = 0;
+       /* Why? because xmalloc_fgets_str doesn't stop on NULs */
+       while ((buffer = xmalloc_fgets_str(procinfo, "\n")) != NULL) {
+               if (proc(lnr++, buffer))
+                       bb_error_msg("%s: bogus data on line %d", file, lnr);
+               free(buffer);
+       }
+       fclose(procinfo);
+}
+
+int netstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int netstat_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       const char *net_conn_line_header = PRINT_NET_CONN_HEADER;
+       unsigned opt;
+#if ENABLE_FEATURE_IPV6
+       smallint inet = 1;
+       smallint inet6 = 1;
+#else
+       enum { inet = 1, inet6 = 0 };
+#endif
+
+       /* 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_showroute) { // -r
+#if ENABLE_ROUTE
+               bb_displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended));
+               return 0;
+#else
+               bb_show_usage();
+#endif
+       }
+
+       if (opt & OPT_widedisplay) { // -W
+               net_conn_line = PRINT_NET_CONN_WIDE;
+               net_conn_line_header = PRINT_NET_CONN_HEADER_WIDE;
+       }
+
+       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");
+       }
+       if (inet && flags & NETSTAT_TCP)
+               do_info(_PATH_PROCNET_TCP, "AF INET (tcp)", tcp_do_one);
+#if ENABLE_FEATURE_IPV6
+       if (inet6 && flags & NETSTAT_TCP)
+               do_info(_PATH_PROCNET_TCP6, "AF INET6 (tcp)", tcp_do_one);
+#endif
+       if (inet && flags & NETSTAT_UDP)
+               do_info(_PATH_PROCNET_UDP, "AF INET (udp)", udp_do_one);
+#if ENABLE_FEATURE_IPV6
+       if (inet6 && flags & NETSTAT_UDP)
+               do_info(_PATH_PROCNET_UDP6, "AF INET6 (udp)", udp_do_one);
+#endif
+       if (inet && flags & NETSTAT_RAW)
+               do_info(_PATH_PROCNET_RAW, "AF INET (raw)", raw_do_one);
+#if ENABLE_FEATURE_IPV6
+       if (inet6 && flags & NETSTAT_RAW)
+               do_info(_PATH_PROCNET_RAW6, "AF INET6 (raw)", raw_do_one);
+#endif
+       if (flags & NETSTAT_UNIX) {
+               printf("Active UNIX domain sockets ");
+               if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) == (NETSTAT_LISTENING|NETSTAT_CONNECTED))
+                       printf("(servers and established)");
+               else if (flags & NETSTAT_LISTENING)
+                       printf("(only servers)");
+               else
+                       printf("(w/o servers)");
+               printf("\nProto RefCnt Flags       Type       State         I-Node Path\n");
+               do_info(_PATH_PROCNET_UNIX, "AF UNIX", unix_do_one);
+       }
+       return 0;
+}
diff --git a/networking/nslookup.c b/networking/nslookup.c
new file mode 100644 (file)
index 0000000..183ae15
--- /dev/null
@@ -0,0 +1,154 @@
+/* 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.
+ */
+
+/* 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;
+
+       server = xmalloc_sockaddr2dotted_noport((struct sockaddr*)&_res.nsaddr_list[0]);
+       /* I honestly don't know what to do if DNS server has _IPv6 address_.
+        * Probably it is listed in
+        * _res._u._ext_.nsaddrs[MAXNS] (of type "struct sockaddr_in6*" each)
+        * but how to find out whether resolver uses
+        * _res.nsaddr_list[] or _res._u._ext_.nsaddrs[], or both?
+        * Looks like classic design from hell, BIND-grade. Hard to surpass. */
+       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.h */
+static void set_default_dns(char *server)
+{
+       struct in_addr server_in_addr;
+
+       if (inet_pton(AF_INET, server, &server_in_addr) > 0) {
+               _res.nscount = 1;
+               _res.nsaddr_list[0].sin_addr = server_in_addr;
+       }
+}
+
+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 (argc < 2 || *argv[1] == '-' || 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 (argc == 3)
+               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..93b2e02
--- /dev/null
@@ -0,0 +1,778 @@
+/* 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 ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, 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:I:4" USE_PING6("6"))
+enum {
+       OPT_QUIET = 1 << 0,
+       OPT_VERBOSE = 1 << 1,
+       OPT_c = 1 << 2,
+       OPT_s = 1 << 3,
+       OPT_I = 1 << 4,
+       OPT_IPV4 = 1 << 5,
+       OPT_IPV6 = (1 << 6) * ENABLE_PING6,
+};
+
+
+struct globals {
+       int pingsock;
+       int if_index;
+       char *opt_I;
+       len_and_sockaddr *source_lsa;
+       unsigned datalen;
+       unsigned long ntransmitted, nreceived, nrepeats, pingcount;
+       uint16_t myid;
+       unsigned tmin, tmax; /* in us */
+       unsigned long long tsum; /* in us, sum of all times */
+       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 opt_I        (G.opt_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 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; \
+       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 pingstats(int junk ATTRIBUTE_UNUSED)
+{
+       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);
+       }
+       exit(nreceived == 0); /* (nreceived == 0) is true (1) -- 'failure' */
+}
+
+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);
+
+       signal(SIGALRM, sp);
+       if (pingcount == 0 || ntransmitted < pingcount) { /* schedule next in 1s */
+               alarm(PINGINTERVAL);
+       } else { /* done, wait for the last ping to come back */
+               /* todo, don't necessarily need to wait so long... */
+               signal(SIGALRM, pingstats);
+               alarm(MAXWAIT);
+       }
+}
+
+static void sendping4(int junk ATTRIBUTE_UNUSED)
+{
+       /* +4 reserves a place for timestamp, which may end up sitting
+        * *after* packet. Saves one if() */
+       struct icmp *pkt = alloca(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 ATTRIBUTE_UNUSED)
+{
+       struct icmp6_hdr *pkt = alloca(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 (opt_I)
+               setsockopt(pingsock, SOL_SOCKET, SO_BINDTODEVICE, opt_I, strlen(opt_I) + 1);
+
+       /* enable broadcast pings */
+       setsockopt_broadcast(pingsock);
+
+       /* set recv buf for broadcast pings */
+       sockopt = 48 * 1024; /* explain why 48k? */
+       setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
+
+       signal(SIGINT, pingstats);
+
+       /* 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 > 0 && 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 (opt_I)
+               setsockopt(pingsock, SOL_SOCKET, SO_BINDTODEVICE, opt_I, strlen(opt_I) + 1);
+
+#ifdef ICMP6_FILTER
+       {
+               struct icmp6_filter filt;
+               if (!(option_mask32 & OPT_VERBOSE)) {
+                       ICMP6_FILTER_SETBLOCKALL(&filt);
+                       ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt);
+               } else {
+                       ICMP6_FILTER_SETPASSALL(&filt);
+               }
+               if (setsockopt(pingsock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+                                          sizeof(filt)) < 0)
+                       bb_error_msg_and_die("setsockopt(ICMP6_FILTER)");
+       }
+#endif /*ICMP6_FILTER*/
+
+       /* enable broadcast pings */
+       setsockopt_broadcast(pingsock);
+
+       /* set recv buf for broadcast pings */
+       sockopt = 48 * 1024; /* explain why 48k? */
+       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, pingstats);
+
+       /* 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 > 0 && 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       len_and_sockaddr *lsa;
+       char *opt_c, *opt_s;
+       USE_PING6(sa_family_t af = AF_UNSPEC;)
+
+       INIT_G();
+
+       datalen = DEFDATALEN;
+
+       /* exactly one argument needed, -v and -q don't mix */
+       opt_complementary = "=1:q--v:v--q";
+       getopt32(argv, OPT_STRING, &opt_c, &opt_s, &opt_I);
+       if (option_mask32 & OPT_c)
+               pingcount = xatoul(opt_c); // -c
+       if (option_mask32 & OPT_s)
+               datalen = xatou16(opt_s); // -s
+       if (option_mask32 & OPT_I) { // -I
+               if_index = if_nametoindex(opt_I);
+               if (!if_index) {
+                       /* TODO: I'm not sure it takes IPv6 unless in [XX:XX..] format */
+                       source_lsa = xdotted2sockaddr(opt_I, 0);
+                       opt_I = NULL; /* don't try to bind to device later */
+               }
+       }
+       myid = (uint16_t) getpid();
+       hostname = argv[optind];
+#if ENABLE_PING6
+       if (option_mask32 & OPT_IPV4)
+               af = AF_INET;
+       if (option_mask32 & 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);
+       pingstats(0);
+       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, char **argv)
+{
+       argv[0] = (char*)"-6";
+       return ping_main(argc + 1, 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..022d212
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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 ATTRIBUTE_UNUSED, 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 */
+       len_and_sockaddr *lsap;
+       int s;
+       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;
+
+       opt_complementary = "=1"; /* exactly one non-option */
+       getopt32(argv, "p: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;
+       rtt_4 = timeout = xatou_range(opt_timeout, 1, INT_MAX/1000 / 4) * 1000;
+       min_rtt = xatou_range(opt_min_rtt, 1, INT_MAX/1000 / 4) * 1000;
+
+       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);
+               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();
+               }
+
+               while (1) {
+                       if (errno == ECONNREFUSED) {
+                               DMSG("port %u: ECONNREFUSED", port);
+                               closed_ports++;
+                               break;
+                       }
+                       DERR("port %u errno %d @%u", port, errno, MONOTONIC_US() - start);
+                       if ((MONOTONIC_US() - start) > rtt_4)
+                               break;
+                       /* Can sleep (much) longer than specified delay.
+                        * We check rtt BEFORE we usleep, otherwise
+                        * on localhost we'll do zero writes done (!)
+                        * before we exceed (rather small) rtt */
+                       usleep(rtt_4/8);
+                       DMSG("write to port %u @%u", port, MONOTONIC_US() - start);
+                       if (write(s, " ", 1) >= 0) { /* We were able to write to the socket */
+ open:
+                               open_ports++;
+                               printf("%5u\ttcp\topen\t%s\n", port, port_name(port));
+                               break;
+                       }
+               }
+               DMSG("out of loop @%u", MONOTONIC_US() - start);
+
+               /* 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 (*4), decrease slowly (4/8 == 1/2)
+                * because we don't want to accidentally miss ports. */
+               rtt_4 = (MONOTONIC_US() - start) * 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 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..53e3988
--- /dev/null
@@ -0,0 +1,700 @@
+/* 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 <getopt.h>
+#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)) {
+               unsigned long mask = mask_in_addr(rt);
+
+               mask = ~ntohl(mask);
+               if ((rt.rt_flags & RTF_HOST) && mask != 0xffffffff) {
+                       bb_error_msg_and_die("netmask %.8x and host route conflict",
+                                                                (unsigned int) mask);
+               }
+               if (mask & (mask + 1)) {
+                       bb_error_msg_and_die("bogus netmask %s", netmask);
+               }
+               mask = ((struct sockaddr_in *) &rt.rt_dst)->sin_addr.s_addr;
+               if (mask & ~mask_in_addr(rt)) {
+                       bb_error_msg_and_die("netmask and route address conflict");
+               }
+       }
+
+       /* Fill out netmask if still unset */
+       if ((action == RTACTION_ADD) && (rt.rt_flags & RTF_HOST)) {
+               mask_in_addr(rt) = 0xffffffff;
+       }
+
+       /* Create a socket to the INET kernel. */
+       skfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+       if (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(ifr.ifr_name, devname, sizeof(ifr.ifr_name));
+               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 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("/proc/net/route", "r");
+
+       printf("Kernel IP routing table\n"
+              "Destination     Gateway         Genmask         Flags %s Iface\n",
+                       netstatfmt ? "  MSS Window  irtt" : "Metric Ref    Use");
+
+       if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the first line. */
+               goto ERROR;                /* Empty or missing line, or read error. */
+       }
+       while (1) {
+               int r;
+               r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n",
+                                  devname, &d, &g, &flgs, &ref, &use, &metric, &m,
+                                  &mtu, &win, &ir);
+               if (r != 11) {
+                       if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
+                               break;
+                       }
+ ERROR:
+                       bb_error_msg_and_die("fscanf");
+               }
+
+               if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */
+                       continue;
+               }
+
+               set_flags(flags, (flgs & IPV4_MASK));
+#ifdef RTF_REJECT
+               if (flgs & RTF_REJECT) {
+                       flags[0] = '!';
+               }
+#endif
+
+               memset(&s_addr, 0, sizeof(struct sockaddr_in));
+               s_addr.sin_family = AF_INET;
+               s_addr.sin_addr.s_addr = d;
+               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("/proc/net/ipv6_route", "r");
+
+       printf("Kernel IPv6 routing table\n%-44s%-40s"
+                         "Flags Metric Ref    Use Iface\n",
+                         "Destination", "Next Hop");
+
+       while (1) {
+               int r;
+               r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n",
+                                  addr6x+14, &prefix_len, &slen, addr6x+40+7,
+                                  &metric, &use, &refcnt, &iflags, iface);
+               if (r != 9) {
+                       if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
+                               break;
+                       }
+ ERROR:
+                       bb_error_msg_and_die("fscanf");
+               }
+
+               /* Do the addr6x shift-and-insert changes to ':'-delimit addresses.
+                * For now, always do this to validate the proc route format, even
+                * if the interface is down. */
+               {
+                       int i = 0;
+                       char *p = addr6x+14;
+
+                       do {
+                               if (!*p) {
+                                       if (i == 40) { /* nul terminator for 1st address? */
+                                               addr6x[39] = 0; /* Fixup... need 0 instead of ':'. */
+                                               ++p;    /* Skip and continue. */
+                                               continue;
+                                       }
+                                       goto ERROR;
+                               }
+                               addr6x[i++] = *p++;
+                               if (!((i+1) % 5)) {
+                                       addr6x[i++] = ':';
+                               }
+                       } while (i < 40+28+7);
+               }
+
+               if (!(iflags & RTF_UP)) { /* Skip interfaces that are down. */
+                       continue;
+               }
+
+               set_flags(flags, (iflags & IPV6_MASK));
+
+               r = 0;
+               do {
+                       inet_pton(AF_INET6, addr6x + r,
+                                         (struct sockaddr *) &snaddr6.sin6_addr);
+                       snaddr6.sin6_family = AF_INET6;
+                       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 ATTRIBUTE_UNUSED, 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/sendmail.c b/networking/sendmail.c
new file mode 100644 (file)
index 0000000..2eb01dc
--- /dev/null
@@ -0,0 +1,587 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones sendmail/fetchmail
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+#define INITIAL_STDIN_FILENO 3
+
+static void uuencode(char *fname, const char *text)
+{
+       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
+       int fd;
+#define len fd
+       char dst_buf[DST_BUF_SIZE + 1];
+
+       if (fname) {
+               fd = INITIAL_STDIN_FILENO;
+               if (NOT_LONE_DASH(fname))
+                       fd = xopen(fname, O_RDONLY);
+               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;
+
+       fflush(stdout); // sync stdio and unistd output
+       while (1) {
+               size_t size;
+               if (fname) {
+                       size = full_read(fd, (char *)src_buf, SRC_BUF_SIZE);
+                       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) {
+                       xwrite(STDOUT_FILENO, "\r\n", 2);
+               } else {
+                       src_buf += size;
+                       len -= size;
+               }
+               xwrite(STDOUT_FILENO, dst_buf, 4 * ((size + 2) / 3));
+       }
+       if (fname)
+               close(fd);
+}
+
+struct globals {
+       pid_t helper_pid;
+       unsigned timeout;
+       // arguments for SSL connection helper
+       const char *xargs[9];
+       // arguments for postprocess helper
+       const char *fargs[3];
+};
+#define G (*ptr_to_globals)
+#define helper_pid      (G.helper_pid)
+#define timeout         (G.timeout   )
+#define xargs           (G.xargs     )
+#define fargs           (G.fargs     )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       xargs[0] = "openssl"; \
+       xargs[1] = "s_client"; \
+       xargs[2] = "-quiet"; \
+       xargs[3] = "-connect"; \
+       /*xargs[4] = "server[:port]";*/ \
+       xargs[5] = "-tls1"; \
+       xargs[6] = "-starttls"; \
+       xargs[7] = "smtp"; \
+       fargs[0] = "utf-8"; \
+} while (0)
+
+#define opt_connect      (xargs[4])
+#define opt_after_connect (xargs[5])
+#define opt_charset      (fargs[0])
+#define opt_subject      (fargs[1])
+
+static void kill_helper(void)
+{
+       // TODO!!!: is there more elegant way to terminate child on program failure?
+       if (helper_pid > 0)
+               kill(helper_pid, SIGTERM);
+}
+
+// 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 (wait_any_nohang(&err) > 0)
+               if (WIFEXITED(err) && WEXITSTATUS(err))
+                       bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
+}
+
+static void launch_helper(const char **argv)
+{
+       // setup vanilla unidirectional pipes interchange
+       int idx;
+       int pipes[4];
+       xpipe(pipes);
+       xpipe(pipes+2);
+       helper_pid = vfork();
+       if (helper_pid < 0)
+               bb_perror_msg_and_die("vfork");
+       idx = (!helper_pid)*2;
+       xdup2(pipes[idx], STDIN_FILENO);
+       xdup2(pipes[3-idx], STDOUT_FILENO);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               for (int i = 4; --i >= 0; )
+                       if (pipes[i] > STDOUT_FILENO)
+                               close(pipes[i]);
+       if (!helper_pid) {
+               // child: try to execute connection helper
+               BB_EXECVP(*argv, (char **)argv);
+               _exit(127);
+       }
+       // parent: check whether child is alive
+       bb_signals(0
+               + (1 << SIGCHLD)
+               + (1 << SIGALRM)
+               , signal_handler);
+       signal_handler(SIGCHLD);
+       // child seems OK -> parent goes on
+}
+
+static const char *command(const char *fmt, const char *param)
+{
+       const char *msg = fmt;
+       alarm(timeout);
+       if (msg) {
+               msg = xasprintf(fmt, param);
+               printf("%s\r\n", msg);
+       }
+       fflush(stdout);
+       return msg;
+}
+
+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_getline(stdin)) != NULL)
+               if (strlen(answer) <= 3 || '-' != answer[3])
+                       break;
+       if (answer) {
+               int n = atoi(answer);
+               alarm(0);
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       free(answer);
+               }
+               if (-1 == code || n == code) {
+                       return n;
+               }
+       }
+       kill_helper();
+       bb_error_msg_and_die("%s failed", msg);
+}
+
+static int inline smtp_check(const char *fmt, int code)
+{
+       return smtp_checkp(fmt, NULL, code);
+}
+
+// strip argument of bad chars
+static char *sane(char *str)
+{
+       char *s = str;
+       char *p = s;
+       while (*s) {
+               if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
+                       *p++ = *s;
+               }
+               s++;
+       }
+       *p = '\0';
+       return str;
+}
+
+#if ENABLE_FETCHMAIL
+static void pop3_checkr(const char *fmt, const char *param, char **ret)
+{
+       const char *msg = command(fmt, param);
+       char *answer = xmalloc_getline(stdin);
+       if (answer && '+' == *answer) {
+               alarm(0);
+               if (ret)
+                       *ret = answer+4; // skip "+OK "
+               else if (ENABLE_FEATURE_CLEAN_UP)
+                       free(answer);
+               return;
+       }
+       kill_helper();
+       bb_error_msg_and_die("%s failed", msg);
+}
+
+static void inline pop3_check(const char *fmt, const char *param)
+{
+       pop3_checkr(fmt, param, NULL);
+}
+
+static void pop3_message(const char *filename)
+{
+       int fd;
+       char *answer;
+       // create and open file filename
+       // read stdin, copy to created file
+       fd = xopen(filename, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL);
+       while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) {
+               char *s = answer;
+               if ('.' == *answer) {
+                       if ('.' == answer[1])
+                               s++;
+                       else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
+                               break;
+               }
+               xwrite(fd, s, strlen(s));
+               free(answer);
+       }
+       close(fd);
+}
+#endif
+
+int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sendgetmail_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       llist_t *opt_recipients = NULL;
+
+       const char *opt_user;
+       const char *opt_pass;
+
+       enum {
+               OPT_w = 1 << 0,         // network timeout
+               OPT_U = 1 << 1,         // user
+               OPT_P = 1 << 2,         // password
+               OPT_X = 1 << 3,         // connect using openssl s_client helper
+
+               OPTS_n = 1 << 4,        // sendmail: request notification
+               OPTF_t = 1 << 4,        // fetchmail: use "TOP" not "RETR"
+
+               OPTS_s = 1 << 5,        // sendmail: subject
+               OPTF_z = 1 << 5,        // fetchmail: delete from server
+
+               OPTS_c = 1 << 6,        // sendmail: assumed charset
+               OPTS_t = 1 << 7,        // sendmail: recipient(s)
+       };
+
+       const char *options;
+       unsigned opts;
+
+       // init global variables
+       INIT_G();
+
+       // parse options, different option sets for sendmail and fetchmail
+       // N.B. opt_after_connect hereafter is NULL if we are called as fetchmail
+       // and is NOT NULL if we are called as sendmail
+       if (!ENABLE_FETCHMAIL || 's' == applet_name[0]) {
+               // SENDMAIL
+               // save initial stdin (body or attachements can be piped!)
+               xdup2(STDIN_FILENO, INITIAL_STDIN_FILENO);
+               opt_complementary = "-2:w+:t:t::"; // count(-t) > 0
+               options = "w:U:P:X" "ns:c:t:";
+       } else {
+               // FETCHMAIL
+               opt_after_connect = NULL;
+               opt_complementary = "-2:w+:P";
+               options = "w:U:P:X" "tz";
+       }
+       opts = getopt32(argv, options,
+               &timeout, &opt_user, &opt_pass,
+               &opt_subject, &opt_charset, &opt_recipients
+       );
+       //argc -= optind;
+       argv += optind;
+
+       // first argument is remote server[:port]
+       opt_connect = *argv++;
+
+       // connect to server
+       // SSL ordered? ->
+       if (opts & OPT_X) {
+               // ... use openssl helper
+               launch_helper(xargs);
+       // no SSL ordered? ->
+       } else {
+               // ... make plain connect
+               int fd = create_and_connect_stream_or_die(opt_connect, 25);
+               // make ourselves a simple IO filter
+               // from now we know nothing about network :)
+               xmove_fd(fd, STDIN_FILENO);
+               xdup2(STDIN_FILENO, STDOUT_FILENO);
+       }
+
+#if ENABLE_FETCHMAIL
+       // we are sendmail?
+       if (opt_after_connect)
+#endif
+       {
+/***************************************************
+ * SENDMAIL
+ ***************************************************/
+
+               char *opt_from;
+               int code;
+               char *boundary;
+               const char *fmt;
+               const char *p;
+               char *q;
+
+               // we didn't use SSL helper? ->
+               if (!(opts & OPT_X)) {
+                       // ... wait for initial server OK
+                       smtp_check(NULL, 220);
+               }
+
+               // get the sender
+               opt_from = sane(*argv++);
+
+               // introduce to server
+               // we should start with modern EHLO
+               if (250 != smtp_checkp("EHLO %s", opt_from, -1)) {
+                       smtp_checkp("HELO %s", opt_from, 250);
+               }
+
+               // set sender
+               // NOTE: if password has not been specified
+               // then no authentication is possible
+               code = (opts & OPT_P) ? -1 : 250;
+               // first try softly without authentication
+               while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) {
+                       // MAIL FROM failed -> authentication needed
+                       // have we got username?
+                       if (!(opts & OPT_U)) {
+                               // no! fetch it from "from" option
+                               //opts |= OPT_U;
+                               opt_user = xstrdup(opt_from);
+                               *strchrnul(opt_user, '@') = '\0';
+                       }
+                       // now we've got username
+                       // so try to authenticate
+                       if (334 == smtp_check("AUTH LOGIN", -1)) {
+                               uuencode(NULL, opt_user);
+                               smtp_check("", 334);
+                               uuencode(NULL, opt_pass);
+                               smtp_check("", 235);
+                       }
+                       // authenticated OK? -> retry to set sender
+                       // but this time die on failure!
+                       code = 250;
+               }
+
+               // set recipients
+               for (llist_t *to = opt_recipients; to; to = to->link) {
+                       smtp_checkp("RCPT TO:<%s>", sane(to->data), 250);
+               }
+
+               // enter "put message" mode
+               smtp_check("DATA", 354);
+
+               // put address headers
+               printf("From: %s\r\n", opt_from);
+               for (llist_t *to = opt_recipients; to; to = to->link) {
+                       printf("To: %s\r\n", to->data);
+               }
+
+               // put encoded subject
+               if (opts & OPTS_c)
+                       sane((char *)opt_charset);
+               if (opts & OPTS_s) {
+                       printf("Subject: =?%s?B?", opt_charset);
+                       uuencode(NULL, opt_subject);
+                       printf("?=\r\n");
+               }
+
+               // put notification
+               if (opts & OPTS_n)
+                       printf("Disposition-Notification-To: %s\r\n", opt_from);
+
+               // make a random string -- it will delimit message parts
+               srand(monotonic_us());
+               boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
+
+               // put common headers and body start
+               printf(
+                       "Message-ID: <%s>\r\n"
+                       "Mime-Version: 1.0\r\n"
+                       "%smultipart/mixed; boundary=\"%s\"\r\n"
+                       , boundary
+                       , "Content-Type: "
+                       , boundary
+               );
+
+               // put body + attachment(s)
+               // N.B. all these weird things just to be tiny
+               // by reusing string patterns!
+               fmt =
+                       "\r\n--%s\r\n"
+                       "%stext/plain; charset=%s\r\n"
+                       "%s%s\r\n"
+                       "%s"
+               ;
+               p = opt_charset;
+               q = (char *)"";
+               while (*argv) {
+                       printf(
+                               fmt
+                               , boundary
+                               , "Content-Type: "
+                               , p
+                               , "Content-Disposition: inline"
+                               , q
+                               , "Content-Transfer-Encoding: base64\r\n"
+                       );
+                       p = "";
+                       fmt =
+                               "\r\n--%s\r\n"
+                               "%sapplication/octet-stream%s\r\n"
+                               "%s; filename=\"%s\"\r\n"
+                               "%s"
+                       ;
+                       uuencode(*argv, NULL);
+                       if (*(++argv))
+                               q = bb_get_last_path_component_strip(*argv);
+               }
+
+               // put message terminator
+               printf("\r\n--%s--\r\n" "\r\n", boundary);
+
+               // leave "put message" mode
+               smtp_check(".", 250);
+               // ... and say goodbye
+               smtp_check("QUIT", 221);
+
+#if ENABLE_FETCHMAIL
+       } else {
+/***************************************************
+ * FETCHMAIL
+ ***************************************************/
+
+               char *buf;
+               unsigned nmsg;
+               char *hostname;
+               pid_t pid;
+
+               // cache fetch command:
+               // TOP will return only the headers
+               // RETR will dump the whole message
+               const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
+
+               // goto maildir
+               xchdir(*argv++);
+
+               // cache postprocess program
+               *fargs = *argv;
+               
+               // authenticate
+               if (!(opts & OPT_U)) {
+                       //opts |= OPT_U;
+                       // N.B. IMHO getenv("USER") can be way easily spoofed!
+                       opt_user = bb_getpwuid(NULL, -1, getuid());
+               }
+
+               // get server greeting
+               pop3_checkr(NULL, NULL, &buf);
+
+               // server supports APOP?
+               if ('<' == *buf) {
+                       md5_ctx_t md5;
+                       // yes! compose <stamp><password>
+                       char *s = strchr(buf, '>');
+                       if (s)
+                               strcpy(s+1, opt_pass);
+                       s = buf;
+                       // get md5 sum of <stamp><password>
+                       md5_begin(&md5);
+                       md5_hash(s, strlen(s), &md5);
+                       md5_end(s, &md5);
+                       // NOTE: md5 struct contains enough space
+                       // so we reuse md5 space instead of xzalloc(16*2+1)
+#define md5_hex ((uint8_t *)&md5)
+//                     uint8_t *md5_hex = (uint8_t *)&md5;
+                       *bin2hex(md5_hex, s, 16) = '\0';
+                       // APOP
+                       s = xasprintf("%s %s", opt_user, md5_hex);
+#undef md5_hex
+                       pop3_check("APOP %s", s);
+                       if (ENABLE_FEATURE_CLEAN_UP) {
+                               free(s);
+                               free(buf-4); // buf is "+OK " away from malloc'ed string
+                       }
+               // server ignores APOP -> use simple text authentication
+               } else {
+                       // USER
+                       pop3_check("USER %s", opt_user);
+                       // PASS
+                       pop3_check("PASS %s", opt_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);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(buf-4); // buf is "+OK " away from malloc'ed string
+
+               // loop through messages
+               for (; nmsg; nmsg--) {
+
+                       // generate unique filename
+                       char *filename = xasprintf("tmp/%llu.%u.%s", monotonic_us(), pid, hostname);
+                       char *target;
+                       int rc;
+
+                       // retrieve message in ./tmp/
+                       pop3_check(retr, (const char *)(ptrdiff_t)nmsg);
+                       pop3_message(filename);
+                       // delete message from server
+                       if (opts & OPTF_z)
+                               pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);
+
+                       // run postprocessing program
+                       if (*fargs) {
+                               fargs[1] = filename;
+                               rc = wait4pid(spawn((char **)fargs));
+                               if (99 == rc)
+                                       break;
+                               if (1 == rc)
+                                       goto skip;
+                       }
+
+                       // atomically move message to ./new/
+                       target = xstrdup(filename);
+                       strncpy(target, "new", 3);
+                       // ... or just stop receiving on error
+                       if (rename_or_warn(filename, target))
+                               break;
+                       free(target);
+ skip:
+                       free(filename);
+               }
+
+               // Bye
+               pop3_check("QUIT", NULL);
+#endif // ENABLE_FETCHMAIL
+       }
+
+       return 0;
+}
diff --git a/networking/slattach.c b/networking/slattach.c
new file mode 100644 (file)
index 0000000..3ffbb3b
--- /dev/null
@@ -0,0 +1,243 @@
+/* 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) ATTRIBUTE_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(1);
+       }
+}
+
+static void sig_handler(int signo ATTRIBUTE_UNUSED)
+{
+       restore_state_and_exit(0);
+}
+
+int slattach_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int slattach_main(int argc ATTRIBUTE_UNUSED, 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);
+       }
+
+       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(0);
+}
diff --git a/networking/tcpudp.c b/networking/tcpudp.c
new file mode 100644 (file)
index 0000000..0b604af
--- /dev/null
@@ -0,0 +1,609 @@
+/* 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/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;
+               *strchrnul(var, '=') = '\0';
+               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 ATTRIBUTE_UNUSED)
+{
+       int wstat;
+       int 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 ATTRIBUTE_UNUSED, 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) {
+               if (!get_uidgid(&ugid, user, 1))
+                       bb_error_msg_and_die("unknown user/group: %s", 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("fatal: cannot 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(SIGTERM, SIG_DFL);
+       signal(SIGPIPE, SIG_DFL);
+       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..9fc8cee
--- /dev/null
@@ -0,0 +1,29 @@
+/* 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.
+ */
+
+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);
diff --git a/networking/telnet.c b/networking/telnet.c
new file mode 100644 (file)
index 0000000..78229cd
--- /dev/null
@@ -0,0 +1,658 @@
+/* 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 <termios.h>
+#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;
+
+struct globals {
+       int     netfd; /* console fd:s are 0 and 1 (and 2) */
+       short   iaclen; /* could even use byte */
+       byte    telstate; /* telnet negotiation state from network input */
+       byte    telwish;  /* DO, DONT, WILL, WONT */
+       byte    charmode;
+       byte    telflags;
+       byte    gotsig;
+       byte    do_termios;
+#if ENABLE_FEATURE_TELNET_TTYPE
+       char    *ttype;
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+       const char *autologin;
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+       int     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 iacflush(void)
+{
+       write(G.netfd, G.iacbuf, G.iaclen);
+       G.iaclen = 0;
+}
+
+#define write_str(fd, str) write(fd, str, sizeof(str) - 1)
+
+static void doexit(int ev) ATTRIBUTE_NORETURN;
+static void doexit(int ev)
+{
+       cookmode();
+       exit(ev);
+}
+
+static void conescape(void)
+{
+       char b;
+
+       if (G.gotsig)   /* 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(0, &b, 1) <= 0)
+               doexit(1);
+
+       switch (b) {
+       case 'l':
+               if (!G.gotsig) {
+                       do_linemode();
+                       goto rrturn;
+               }
+               break;
+       case 'c':
+               if (G.gotsig) {
+                       will_charmode();
+                       goto rrturn;
+               }
+               break;
+       case 'z':
+               cookmode();
+               kill(0, SIGTSTP);
+               rawmode();
+               break;
+       case 'e':
+               doexit(0);
+       }
+
+       write_str(1, "continuing...\r\n");
+
+       if (G.gotsig)
+               cookmode();
+
+ rrturn:
+       G.gotsig = 0;
+
+}
+
+static void handlenetoutput(int len)
+{
+       /* here we could do smart tricks how to handle 0xFF:s in output
+        * stream like writing twice every sequence of FF:s (thus doing
+        * many write()s. But I think interactive telnet application does
+        * not need to be 100% 8-bit clean, so changing every 0xff:s to
+        * 0x7f:s
+        *
+        * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com)
+        * I don't agree.
+        * first - I cannot use programs like sz/rz
+        * second - the 0x0D is sent as one character and if the next
+        *      char is 0x0A then it's eaten by a server side.
+        * third - whay doy you have to make 'many write()s'?
+        *      I don't understand.
+        * So I implemented it. It's realy useful for me. I hope that
+        * others people will find it interesting 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) {
+                       conescape();
+                       return;
+               }
+               outbuf[j++] = *p;
+               if (*p == 0xff)
+                       outbuf[j++] = 0xff;
+               else if (*p == 0x0d)
+                       outbuf[j++] = 0x00;
+       }
+       if (j > 0)
+               write(G.netfd, outbuf, j);
+}
+
+static void handlenetinput(int len)
+{
+       int i;
+       int cstart = 0;
+
+       for (i = 0; i < len; i++) {
+               byte c = G.buf[i];
+
+               if (G.telstate == 0) { /* most of the time state == 0 */
+                       if (c == IAC) {
+                               cstart = i;
+                               G.telstate = TS_IAC;
+                       }
+               } else
+                       switch (G.telstate) {
+                       case TS_0:
+                               if (c == IAC)
+                                       G.telstate = TS_IAC;
+                               else
+                                       G.buf[cstart++] = c;
+                               break;
+
+                       case TS_IAC:
+                               if (c == IAC) { /* IAC IAC -> 0xFF */
+                                       G.buf[cstart++] = c;
+                                       G.telstate = TS_0;
+                                       break;
+                               }
+                               /* else */
+                               switch (c) {
+                               case SB:
+                                       G.telstate = TS_SUB1;
+                                       break;
+                               case DO:
+                               case DONT:
+                               case WILL:
+                               case WONT:
+                                       G.telwish =  c;
+                                       G.telstate = TS_OPT;
+                                       break;
+                               default:
+                                       G.telstate = TS_0;      /* DATA MARK must be added later */
+                               }
+                               break;
+                       case TS_OPT: /* WILL, WONT, DO, DONT */
+                               telopt(c);
+                               G.telstate = TS_0;
+                               break;
+                       case TS_SUB1: /* Subnegotiation */
+                       case TS_SUB2: /* Subnegotiation */
+                               if (subneg(c))
+                                       G.telstate = TS_0;
+                               break;
+                       }
+       }
+       if (G.telstate) {
+               if (G.iaclen) iacflush();
+               if (G.telstate == TS_0) G.telstate = 0;
+               len = cstart;
+       }
+
+       if (len)
+               write(1, G.buf, len);
+}
+
+static void putiac(int c)
+{
+       G.iacbuf[G.iaclen++] = c;
+}
+
+static void putiac2(byte wwdd, byte c)
+{
+       if (G.iaclen + 3 > IACBUFSIZE)
+               iacflush();
+
+       putiac(IAC);
+       putiac(wwdd);
+       putiac(c);
+}
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void putiac_subopt(byte c, char *str)
+{
+       int     len = strlen(str) + 6;   // ( 2 + 1 + 1 + strlen + 2 )
+
+       if (G.iaclen + len > IACBUFSIZE)
+               iacflush();
+
+       putiac(IAC);
+       putiac(SB);
+       putiac(c);
+       putiac(0);
+
+       while (*str)
+               putiac(*str++);
+
+       putiac(IAC);
+       putiac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void putiac_subopt_autologin(void)
+{
+       int len = strlen(G.autologin) + 6;      // (2 + 1 + 1 + strlen + 2)
+       const char *user = "USER";
+
+       if (G.iaclen + len > IACBUFSIZE)
+               iacflush();
+
+       putiac(IAC);
+       putiac(SB);
+       putiac(TELOPT_NEW_ENVIRON);
+       putiac(TELQUAL_IS);
+       putiac(NEW_ENV_VAR);
+
+       while (*user)
+               putiac(*user++);
+
+       putiac(NEW_ENV_VALUE);
+
+       while (*G.autologin)
+               putiac(*G.autologin++);
+
+       putiac(IAC);
+       putiac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void putiac_naws(byte c, int x, int y)
+{
+       if (G.iaclen + 9 > IACBUFSIZE)
+               iacflush();
+
+       putiac(IAC);
+       putiac(SB);
+       putiac(c);
+
+       putiac((x >> 8) & 0xff);
+       putiac(x & 0xff);
+       putiac((y >> 8) & 0xff);
+       putiac(y & 0xff);
+
+       putiac(IAC);
+       putiac(SE);
+}
+#endif
+
+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();
+
+       putiac2(DO, TELOPT_ECHO);
+       putiac2(DO, TELOPT_SGA);
+       iacflush();
+}
+
+static void do_linemode(void)
+{
+       G.charmode = CHM_TRY;
+       G.telflags &= ~(UF_ECHO | UF_SGA);
+       setConMode();
+
+       putiac2(DONT, TELOPT_ECHO);
+       putiac2(DONT, TELOPT_SGA);
+       iacflush();
+}
+
+static void to_notsup(char c)
+{
+       if (G.telwish == WILL)
+               putiac2(DONT, c);
+       else if (G.telwish == DO)
+               putiac2(WONT, c);
+}
+
+static void to_echo(void)
+{
+       /* if server requests ECHO, don't agree */
+       if (G.telwish == DO) {
+               putiac2(WONT, TELOPT_ECHO);
+               return;
+       }
+       if (G.telwish == DONT)
+               return;
+
+       if (G.telflags & UF_ECHO) {
+               if (G.telwish == WILL)
+                       return;
+       } else if (G.telwish == WONT)
+               return;
+
+       if (G.charmode != CHM_OFF)
+               G.telflags ^= UF_ECHO;
+
+       if (G.telflags & UF_ECHO)
+               putiac2(DO, TELOPT_ECHO);
+       else
+               putiac2(DONT, TELOPT_ECHO);
+
+       setConMode();
+       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)
+               putiac2(DO, TELOPT_SGA);
+       else
+               putiac2(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)
+               putiac2(WILL, TELOPT_TTYPE);
+       else
+               putiac2(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)
+               putiac2(WILL, TELOPT_NEW_ENVIRON);
+       else
+               putiac2(WONT, TELOPT_NEW_ENVIRON);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void to_naws(void)
+{
+       /* Tell server we will do NAWS */
+       putiac2(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();
+               putiac_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)
+                       putiac_subopt(TELOPT_TTYPE, G.ttype);
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+               else
+               if (c == TELOPT_NEW_ENVIRON)
+                       putiac_subopt_autologin();
+#endif
+               break;
+       case TS_SUB2:
+               if (c == SE)
+                       return TRUE;
+               G.telstate = TS_SUB1;
+               /* break; */
+       }
+       return FALSE;
+}
+
+static void fgotsig(int sig)
+{
+       G.gotsig = sig;
+}
+
+
+static void rawmode(void)
+{
+       if (G.do_termios)
+               tcsetattr(0, TCSADRAIN, &G.termios_raw);
+}
+
+static void cookmode(void)
+{
+       if (G.do_termios)
+               tcsetattr(0, TCSADRAIN, &G.termios_def);
+}
+
+/* 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, 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 (argc < 2)
+               bb_show_usage();
+
+#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();
+
+       G.netfd = create_and_connect_stream_or_die(host, port);
+
+       setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+       signal(SIGINT, fgotsig);
+
+#ifdef USE_POLL
+       ufds[0].fd = 0; ufds[1].fd = G.netfd;
+       ufds[0].events = ufds[1].events = POLLIN;
+#else
+       FD_ZERO(&readfds);
+       FD_SET(0, &readfds);
+       FD_SET(G.netfd, &readfds);
+       maxfd = G.netfd + 1;
+#endif
+
+       while (1) {
+#ifndef USE_POLL
+               fd_set rfds = readfds;
+
+               switch (select(maxfd, &rfds, NULL, NULL, NULL))
+#else
+               switch (poll(ufds, 2, -1))
+#endif
+               {
+               case 0:
+                       /* timeout */
+               case -1:
+                       /* error, ignore and/or log something, bay go to loop */
+                       if (G.gotsig)
+                               conescape();
+                       else
+                               sleep(1);
+                       break;
+               default:
+
+#ifdef USE_POLL
+                       if (ufds[0].revents) /* well, should check POLLIN, but ... */
+#else
+                       if (FD_ISSET(0, &rfds))
+#endif
+                       {
+                               len = read(0, G.buf, DATABUFSIZE);
+                               if (len <= 0)
+                                       doexit(0);
+                               TRACE(0, ("Read con: %d\n", len));
+                               handlenetoutput(len);
+                       }
+
+#ifdef USE_POLL
+                       if (ufds[1].revents) /* well, should check POLLIN, but ... */
+#else
+                       if (FD_ISSET(G.netfd, &rfds))
+#endif
+                       {
+                               len = read(G.netfd, G.buf, DATABUFSIZE);
+                               if (len <= 0) {
+                                       write_str(1, "Connection closed by foreign host\r\n");
+                                       doexit(1);
+                               }
+                               TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len));
+                               handlenetinput(len);
+                       }
+               }
+       }
+}
diff --git a/networking/telnetd.c b/networking/telnetd.c
new file mode 100644 (file)
index 0000000..5188edb
--- /dev/null
@@ -0,0 +1,600 @@
+/* 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;
+       int sockfd_read, sockfd_write, ptyfd;
+       int shell_pid;
+
+       /* 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.
+
+   FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
+   what is the escape character?  We aren't handling that situation here.
+
+   CR-LF ->'s CR mapping is also done here, for convenience.
+
+   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 now map \r\n ==> \r for pragmatic reasons.
+                        * Many client implementations send \r\n when
+                        * the user hits the CarriageReturn key.
+                        */
+                       if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
+                               ptr++;
+               } else {
+                       /*
+                        * TELOPT_NAWS support!
+                        */
+                       if ((ptr+2) >= end) {
+                               /* only the beginning of the IAC is in the
+                               buffer we were asked to process, we can't
+                               process this char. */
+                               break;
+                       }
+
+                       /*
+                        * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
+                        */
+                       else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
+                               struct winsize ws;
+
+                               if ((ptr+8) >= end)
+                                       break;  /* incomplete, can't process */
+                               ws.ws_col = (ptr[3] << 8) | ptr[4];
+                               ws.ws_row = (ptr[5] << 8) | ptr[6];
+                               ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
+                               ptr += 9;
+                       } else {
+                               /* skip 3-byte IAC non-SB cmd */
+#if DEBUG
+                               fprintf(stderr, "Ignoring IAC %s,%s\n",
+                                       TELCMD(ptr[1]), TELOPT(ptr[2]));
+#endif
+                               ptr += 3;
+                       }
+               }
+       }
+
+       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);
+}
+
+
+static struct tsession *
+make_new_session(
+               USE_FEATURE_TELNETD_STANDALONE(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 = getpty(tty_name);
+       if (fd < 0) {
+               bb_error_msg("can't create pty");
+               return NULL;
+       }
+       if (fd > maxfd)
+               maxfd = fd;
+       ts->ptyfd = fd;
+       ndelay_on(fd);
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       ts->sockfd_read = sock;
+       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
+       /* 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,
+                       IAC, DO, TELOPT_LFLOW,
+                       IAC, WILL, TELOPT_ECHO,
+                       IAC, WILL, TELOPT_SGA
+               };
+               memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
+               ts->rdidx2 = sizeof(iacs_to_send);
+               ts->size2 = sizeof(iacs_to_send);
+       }
+
+       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! */
+
+       /* make new session and process group */
+       setsid();
+
+       /* Restore default signal handling */
+       bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
+
+       /* 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! */
+       fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
+       dup2(fd, 0);
+       dup2(fd, 1);
+       dup2(fd, 2);
+       while (fd > 2) close(fd--);
+       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(0, 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, NULL);
+
+       /* 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(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
+}
+
+/* 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 */
+};
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+
+static void
+free_session(struct tsession *ts)
+{
+       struct tsession *t = sessions;
+
+       if (option_mask32 & OPT_INETD)
+               exit(0);
+
+       /* 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);
+       wait4(ts->shell_pid, NULL, 0, NULL);
+#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(0). */
+#define free_session(ts) return 0
+
+#endif
+
+static void handle_sigchld(int sig ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, 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, 0, LOG_USER);
+               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(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(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 = safe_write(ts->sockfd_write, 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/tftp.c b/networking/tftp.c
new file mode 100644 (file)
index 0000000..1432797
--- /dev/null
@@ -0,0 +1,724 @@
+/* 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, 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_DEBUG_TFTP
+       bb_error_msg("using blksize %u", blksize);
+#endif
+       return blksize;
+}
+
+static char *tftp_get_blksize(char *buf, int len)
+{
+#define option "blksize"
+       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;
+#undef option
+}
+
+#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,)
+               int blksize)
+{
+#if !ENABLE_TFTP
+#define remote_file NULL
+#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 = getpwnam(user_opt);
+                       if (!pw)
+                               bb_error_msg_and_die("unknown user '%s'", 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_or_warn(local_file, open_mode);
+               if (local_fd < 0) {
+                       /*error_pkt_reason = ERR_NOFILE/ERR_ACCESS?*/
+                       strcpy(error_pkt_str, "can't open file");
+                       goto send_err_pkt;
+               }
+       }
+
+       if (!ENABLE_TFTP || our_lsa) {
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+               if (blksize != TFTP_BLKSIZE_DEFAULT) {
+                       /* 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 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:
+               /* 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_DEBUG_TFTP
+               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_DEBUG_TFTP
+               fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, recv_blk);
+#endif
+
+               if (opcode == TFTP_ERROR) {
+                       static const char errcode_str[] =
+                               "\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_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(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(error_pkt_str, bb_msg_read_error);
+ send_err_pkt:
+       if (error_pkt_str[0])
+               bb_error_msg(error_pkt_str);
+       error_pkt[1] = TFTP_ERROR;
+       xsendto(socket_fd, error_pkt, 4 + 1 + strlen(error_pkt_str),
+                       &peer_lsa->u.sa, peer_lsa->len);
+       return EXIT_FAILURE;
+}
+
+#if ENABLE_TFTP
+
+int tftp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tftp_main(int argc ATTRIBUTE_UNUSED, 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;
+#endif
+       int blksize;
+       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;
+       }
+#else
+       blksize = TFTP_BLKSIZE_DEFAULT;
+#endif
+
+       if (!local_file)
+               local_file = remote_file;
+       if (!remote_file)
+               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_DEBUG_TFTP
+       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,
+                       blksize);
+
+       if (result != EXIT_SUCCESS && NOT_LONE_DASH(local_file) && CMD_GET(opt)) {
+               unlink(local_file);
+       }
+       return result;
+}
+
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_TFTPD
+
+/* TODO: libbb candidate? */
+static len_and_sockaddr *get_sock_lsa(int s)
+{
+       len_and_sockaddr *lsa;
+       socklen_t len = 0;
+
+       if (getsockname(s, NULL, &len) != 0)
+               return NULL;
+       lsa = xzalloc(LSA_LEN_SIZE + len);
+       lsa->len = len;
+       getsockname(s, &lsa->u.sa, &lsa->len);
+       return lsa;
+}
+
+int tftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tftpd_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       len_and_sockaddr *our_lsa;
+       len_and_sockaddr *peer_lsa;
+       char *local_file, *mode;
+       const char *error_msg;
+       int opt, result, opcode;
+       int blksize = TFTP_BLKSIZE_DEFAULT;
+
+       INIT_G();
+
+       our_lsa = get_sock_lsa(STDIN_FILENO);
+       if (!our_lsa)
+               bb_perror_msg_and_die("stdin is not a socket");
+       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_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;
+                               }
+                       }
+               }
+       }
+#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 */
+       }
+
+       close(STDIN_FILENO); /* close old, possibly wildcard socket */
+       /* tftp_protocol() will create new one, bound to particular local IP */
+
+       /* NB: if error_pkt_str or error_pkt_reason is set up,
+        * tftp_protocol() just sends one error pkt and returns */
+ do_proto:
+       result = tftp_protocol(
+               our_lsa, peer_lsa,
+               local_file, USE_TFTP(NULL /*remote_file*/,)
+               blksize
+       );
+
+       return result;
+ err:
+       strcpy(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..c0b4a3f
--- /dev/null
@@ -0,0 +1,1355 @@
+/* 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"
+
+
+/*
+ * Definitions for internet protocol version 4.
+ * Per RFC 791, September 1981.
+ */
+#define IPVERSION 4
+
+#ifndef IPPROTO_ICMP
+/* Grrrr.... */
+#define IPPROTO_ICMP 1
+#endif
+#ifndef IPPROTO_IP
+#define IPPROTO_IP 0
+#endif
+
+/*
+ * Overlay for ip header used by other protocols (tcp, udp).
+ */
+struct ipovly {
+       unsigned char  ih_x1[9];               /* (unused) */
+       unsigned char  ih_pr;                  /* protocol */
+       short   ih_len;                 /* protocol length */
+       struct  in_addr ih_src;         /* source internet address */
+       struct  in_addr ih_dst;         /* destination internet address */
+};
+
+/*
+ * UDP kernel structures and variables.
+ */
+struct udpiphdr {
+       struct  ipovly ui_i;            /* overlaid ip structure */
+       struct  udphdr ui_u;            /* udp header */
+};
+#define ui_next         ui_i.ih_next
+#define ui_prev         ui_i.ih_prev
+#define ui_x1           ui_i.ih_x1
+#define ui_pr           ui_i.ih_pr
+#define ui_len          ui_i.ih_len
+#define ui_src          ui_i.ih_src
+#define ui_dst          ui_i.ih_dst
+#define ui_sport        ui_u.uh_sport
+#define ui_dport        ui_u.uh_dport
+#define ui_ulen         ui_u.uh_ulen
+#define ui_sum          ui_u.uh_sum
+
+
+/* Host name and address list */
+struct hostinfo {
+       char *name;
+       int n;
+       uint32_t *addrs;
+};
+
+/* Data section of the probe packet */
+typedef struct outdata {
+       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 ATTRIBUTE_PACKED; /* time packet left */
+} outdata_t;
+
+struct IFADDRLIST {
+       uint32_t addr;
+       char device[sizeof(struct ifreq)];
+};
+
+
+/* Keep in sync with getopt32 call! */
+#define OPT_DONT_FRAGMNT (1<<0)    /* F */
+#define OPT_USE_ICMP     (1<<1)    /* I */
+#define OPT_TTL_FLAG     (1<<2)    /* l */
+#define OPT_ADDR_NUM     (1<<3)    /* n */
+#define OPT_BYPASS_ROUTE (1<<4)    /* r */
+#define OPT_DEBUG        (1<<5)    /* d */
+#define OPT_VERBOSE      (1<<6)    /* v */
+#define OPT_IP_CHKSUM    (1<<7)    /* x */
+#define OPT_TOS          (1<<8)    /* t */
+#define OPT_DEVICE       (1<<9)    /* i */
+#define OPT_MAX_TTL      (1<<10)   /* m */
+#define OPT_PORT         (1<<11)   /* p */
+#define OPT_NPROBES      (1<<12)   /* q */
+#define OPT_SOURCE       (1<<13)   /* s */
+#define OPT_WAITTIME     (1<<14)   /* w */
+#define OPT_PAUSE_MS     (1<<15)   /* z */
+#define OPT_FIRST_TTL    (1<<16)   /* f */
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+/* use icmp echo instead of udp packets */
+#define useicmp (option_mask32 & OPT_USE_ICMP)
+#endif
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+#define verbose (option_mask32 & OPT_VERBOSE)
+#endif
+#define nflag   (option_mask32 & OPT_ADDR_NUM)
+
+
+struct globals {
+       struct ip *outip;               /* last output (udp) packet */
+       struct udphdr *outudp;          /* last output (udp) packet */
+       struct outdata *outdata;        /* last output (udp) packet */
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+       struct icmp *outicmp;           /* last output (icmp) packet */
+#endif
+
+       int rcvsock;                    /* receive (icmp) socket file descriptor */
+       int sndsock;                    /* send (udp/icmp) socket file descriptor */
+
+       int packlen;                    /* total length of packet */
+       int minpacket;                  /* min ip packet size */
+       int maxpacket; // 32 * 1024;    /* max ip packet size */
+       int pmtu;                       /* Path MTU Discovery (RFC1191) */
+
+       char *hostname;
+
+       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) */
+       int doipcksum; // 1;            /* calculate ip checksums by default */
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       int optlen;                     /* length of ip options */
+#else
+#define optlen 0
+#endif
+
+       struct sockaddr_storage whereto;        /* Who to try to reach */
+       struct sockaddr_storage wherefrom;      /* Who we are */
+       /* last inbound (icmp) packet */
+       unsigned char packet[512];
+#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 outudp    (G.outudp   )
+#define outdata   (G.outdata  )
+#define outicmp   (G.outicmp  )
+#define rcvsock   (G.rcvsock  )
+#define sndsock   (G.sndsock  )
+#define packlen   (G.packlen  )
+#define minpacket (G.minpacket)
+#define maxpacket (G.maxpacket)
+#define pmtu      (G.pmtu     )
+#define hostname  (G.hostname )
+#define ident     (G.ident    )
+#define port      (G.port     )
+#define waittime  (G.waittime )
+#define doipcksum (G.doipcksum)
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+#define optlen    (G.optlen   )
+#endif
+#define packet    (G.packet   )
+#define whereto   (G.whereto  )
+#define wherefrom (G.wherefrom)
+#define gwlist    (G.gwlist   )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       maxpacket = 32 * 1024; \
+       port = 32768 + 666; \
+       waittime = 5; \
+       doipcksum = 1; \
+} while (0)
+
+
+/*
+ * Return the interface list
+ */
+static int
+ifaddrlist(struct IFADDRLIST **ipaddrp)
+{
+       enum { IFREQ_BUFSIZE = (32 * 1024) / sizeof(struct ifreq) };
+
+       int fd, nipaddr;
+#ifdef HAVE_SOCKADDR_SA_LEN
+       int n;
+#endif
+       struct ifreq *ifrp, *ifend, *ifnext;
+       struct sockaddr_in *addr_sin;
+       struct IFADDRLIST *al;
+       struct ifconf ifc;
+       struct ifreq ifr;
+       /* Was on stack, but 32k is a bit too much: */
+       struct ifreq *ibuf = xmalloc(IFREQ_BUFSIZE * sizeof(ibuf[0]));
+       struct IFADDRLIST *st_ifaddrlist;
+
+       fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+       ifc.ifc_len = IFREQ_BUFSIZE * sizeof(ibuf[0]);
+       ifc.ifc_buf = (caddr_t)ibuf;
+
+       if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0
+        || ifc.ifc_len < sizeof(struct ifreq)
+       ) {
+               if (errno == EINVAL)
+                       bb_error_msg_and_die(
+                           "SIOCGIFCONF: ifreq struct too small (%u bytes)",
+                           (unsigned)(IFREQ_BUFSIZE * sizeof(ibuf[0])));
+               bb_perror_msg_and_die("SIOCGIFCONF");
+       }
+       ifrp = ibuf;
+       ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len);
+
+       nipaddr = 1 + (ifc.ifc_len / sizeof(struct ifreq));
+       st_ifaddrlist = xzalloc(nipaddr * sizeof(struct IFADDRLIST));
+       al = st_ifaddrlist;
+       nipaddr = 0;
+
+       for (; ifrp < ifend; ifrp = ifnext) {
+#ifdef HAVE_SOCKADDR_SA_LEN
+               n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name);
+               if (n < sizeof(*ifrp))
+                       ifnext = ifrp + 1;
+               else
+                       ifnext = (struct ifreq *)((char *)ifrp + n);
+               if (ifrp->ifr_addr.sa_family != AF_INET)
+                       continue;
+#else
+               ifnext = ifrp + 1;
+#endif
+               /*
+                * Need a template to preserve address info that is
+                * used below to locate the next entry.  (Otherwise,
+                * SIOCGIFFLAGS stomps over it because the requests
+                * are returned in a union.)
+                */
+               strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name));
+               if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0) {
+                       if (errno == ENXIO)
+                               continue;
+                       bb_perror_msg_and_die("SIOCGIFFLAGS: %.*s",
+                           (int)sizeof(ifr.ifr_name), ifr.ifr_name);
+               }
+
+               /* Must be up */
+               if ((ifr.ifr_flags & IFF_UP) == 0)
+                       continue;
+
+               safe_strncpy(al->device, ifr.ifr_name, sizeof(ifr.ifr_name) + 1);
+#ifdef sun
+               /* Ignore sun virtual interfaces */
+               if (strchr(al->device, ':') != NULL)
+                       continue;
+#endif
+               ioctl_or_perror_and_die(fd, SIOCGIFADDR, (char *)&ifr,
+                               "SIOCGIFADDR: %s", al->device);
+
+               addr_sin = (struct sockaddr_in *)&ifr.ifr_addr;
+               al->addr = addr_sin->sin_addr.s_addr;
+               ++al;
+               ++nipaddr;
+       }
+       if (nipaddr == 0)
+               bb_error_msg_and_die("can't find any network interfaces");
+
+       free(ibuf);
+       close(fd);
+       *ipaddrp = st_ifaddrlist;
+       return nipaddr;
+}
+
+
+static void
+setsin(struct sockaddr_in *addr_sin, uint32_t addr)
+{
+       memset(addr_sin, 0, sizeof(*addr_sin));
+#ifdef HAVE_SOCKADDR_SA_LEN
+       addr_sin->sin_len = sizeof(*addr_sin);
+#endif
+       addr_sin->sin_family = AF_INET;
+       addr_sin->sin_addr.s_addr = addr;
+}
+
+
+/*
+ * Return the source address for the given destination address
+ */
+static void
+findsaddr(const struct sockaddr_in *to, struct sockaddr_in *from)
+{
+       int i, n;
+       FILE *f;
+       uint32_t mask;
+       uint32_t dest, tmask;
+       struct IFADDRLIST *al;
+       char buf[256], tdevice[256], device[256];
+
+       f = xfopen("/proc/net/route", "r");
+
+       /* Find the appropriate interface */
+       n = 0;
+       mask = 0;
+       device[0] = '\0';
+       while (fgets(buf, sizeof(buf), f) != NULL) {
+               ++n;
+               if (n == 1 && strncmp(buf, "Iface", 5) == 0)
+                       continue;
+               i = sscanf(buf, "%255s %x %*s %*s %*s %*s %*s %x",
+                                       tdevice, &dest, &tmask);
+               if (i != 3)
+                       bb_error_msg_and_die("junk in buffer");
+               if ((to->sin_addr.s_addr & tmask) == dest
+                && (tmask > mask || mask == 0)
+               ) {
+                       mask = tmask;
+                       strcpy(device, tdevice);
+               }
+       }
+       fclose(f);
+
+       if (device[0] == '\0')
+               bb_error_msg_and_die("can't find interface");
+
+       /* Get the interface address list */
+       n = ifaddrlist(&al);
+
+       /* Find our appropriate source address */
+       for (i = n; i > 0; --i, ++al)
+               if (strcmp(device, al->device) == 0)
+                       break;
+       if (i <= 0)
+               bb_error_msg_and_die("can't find interface %s", device);
+
+       setsin(from, al->addr);
+}
+
+/*
+"Usage: %s [-dFIlnrvx] [-g gateway] [-i iface] [-f first_ttl]\n"
+"\t[-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] [-t tos]\n"
+"\t[-w waittime] [-z pausemsecs] host [packetlen]"
+
+*/
+
+static int
+wait_for_reply(int sock, struct sockaddr_in *fromp)
+{
+       struct pollfd pfd[1];
+       int cc = 0;
+       socklen_t fromlen = sizeof(*fromp);
+
+       pfd[0].fd = sock;
+       pfd[0].events = POLLIN;
+       if (safe_poll(pfd, 1, waittime * 1000) > 0)
+               cc = recvfrom(sock, packet, sizeof(packet), 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 cc;
+       struct udpiphdr *ui, *oui;
+       struct ip tip;
+
+       outip->ip_ttl = ttl;
+       outip->ip_id = htons(ident + seq);
+
+       /*
+        * In most cases, the kernel will recalculate the ip checksum.
+        * But we must do it anyway so that the udp checksum comes out
+        * right.
+        */
+       if (doipcksum) {
+               outip->ip_sum =
+                   in_cksum((uint16_t *)outip, sizeof(*outip) + optlen);
+               if (outip->ip_sum == 0)
+                       outip->ip_sum = 0xffff;
+       }
+
+       /* 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 ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+       if (useicmp)
+               outicmp->icmp_seq = htons(seq);
+       else
+#endif
+               outudp->dest = htons(port + seq);
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+       if (useicmp) {
+               /* Always calculate checksum for icmp packets */
+               outicmp->icmp_cksum = 0;
+               outicmp->icmp_cksum = in_cksum((uint16_t *)outicmp,
+                   packlen - (sizeof(*outip) + optlen));
+               if (outicmp->icmp_cksum == 0)
+                       outicmp->icmp_cksum = 0xffff;
+       } else
+#endif
+       if (doipcksum) {
+               /* Checksum (we must save and restore ip header) */
+               tip = *outip;
+               ui = (struct udpiphdr *)outip;
+               oui = (struct udpiphdr *)&tip;
+               /* Easier to zero and put back things that are ok */
+               memset((char *)ui, 0, sizeof(ui->ui_i));
+               ui->ui_src = oui->ui_src;
+               ui->ui_dst = oui->ui_dst;
+               ui->ui_pr = oui->ui_pr;
+               ui->ui_len = outudp->len;
+               outudp->check = 0;
+               outudp->check = in_cksum((uint16_t *)ui, packlen);
+               if (outudp->check == 0)
+                       outudp->check = 0xffff;
+               *outip = tip;
+       }
+
+#if 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_HDRINCL) && defined(IP_TTL)
+       if (setsockopt(sndsock, IPPROTO_IP, IP_TTL,
+           (char *)&ttl, sizeof(ttl)) < 0) {
+               bb_perror_msg_and_die("setsockopt ttl %d", ttl);
+       }
+#endif
+
+       cc = xsendto(sndsock, (char *)outip,
+           packlen, (struct sockaddr *)&whereto, sizeof(whereto));
+       if (cc != packlen)  {
+               bb_info_msg("wrote %s %d chars, ret=%d", hostname, packlen, cc);
+       }
+}
+
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+/*
+ * Convert an ICMP "type" field to a printable string.
+ */
+static inline const char *
+pr_type(unsigned char t)
+{
+       static const char *const ttab[] = {
+       "Echo Reply",   "ICMP 1",       "ICMP 2",       "Dest Unreachable",
+       "Source Quench", "Redirect",    "ICMP 6",       "ICMP 7",
+       "Echo",         "Router Advert", "Router Solicit", "Time Exceeded",
+       "Param Problem", "Timestamp",   "Timestamp Reply", "Info Request",
+       "Info Reply",   "Mask Request", "Mask Reply"
+       };
+
+       if (t > 18)
+               return "OUT-OF-RANGE";
+
+       return ttab[t];
+}
+#endif
+
+#if !ENABLE_FEATURE_TRACEROUTE_VERBOSE
+#define packet_ok(buf, cc, from, seq) \
+       packet_ok(buf, cc, seq)
+#endif
+static int
+packet_ok(unsigned char *buf, int cc, struct sockaddr_in *from, int seq)
+{
+       struct icmp *icp;
+       unsigned char type, code;
+       int hlen;
+       struct ip *ip;
+
+       ip = (struct ip *) buf;
+       hlen = ip->ip_hl << 2;
+       if (cc < hlen + ICMP_MINLEN) {
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+               if (verbose)
+                       printf("packet too short (%d bytes) from %s\n", cc,
+                               inet_ntoa(from->sin_addr));
+#endif
+               return 0;
+       }
+       cc -= hlen;
+       icp = (struct icmp *)(buf + hlen);
+       type = icp->icmp_type;
+       code = icp->icmp_code;
+       /* Path MTU Discovery (RFC1191) */
+       if (code != ICMP_UNREACH_NEEDFRAG)
+               pmtu = 0;
+       else {
+               pmtu = ntohs(icp->icmp_nextmtu);
+       }
+       if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS) ||
+           type == ICMP_UNREACH || type == ICMP_ECHOREPLY) {
+               struct ip *hip;
+               struct udphdr *up;
+
+               hip = &icp->icmp_ip;
+               hlen = hip->ip_hl << 2;
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+               if (useicmp) {
+                       struct icmp *hicmp;
+
+                       /* XXX */
+                       if (type == ICMP_ECHOREPLY &&
+                           icp->icmp_id == htons(ident) &&
+                           icp->icmp_seq == htons(seq))
+                               return -2;
+
+                       hicmp = (struct icmp *)((unsigned char *)hip + hlen);
+                       /* XXX 8 is a magic number */
+                       if (hlen + 8 <= cc &&
+                           hip->ip_p == IPPROTO_ICMP &&
+                           hicmp->icmp_id == htons(ident) &&
+                           hicmp->icmp_seq == htons(seq))
+                               return (type == ICMP_TIMXCEED ? -1 : code + 1);
+               } else
+#endif
+               {
+                       up = (struct udphdr *)((unsigned char *)hip + hlen);
+                       /* XXX 8 is a magic number */
+                       if (hlen + 12 <= cc &&
+                           hip->ip_p == IPPROTO_UDP &&
+                           up->source == htons(ident) &&
+                           up->dest == htons(port + seq))
+                               return (type == ICMP_TIMXCEED ? -1 : code + 1);
+               }
+       }
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+       if (verbose) {
+               int i;
+               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 nflag has been supplied, give
+ * numeric value, otherwise try for symbolic name.
+ */
+static inline void
+print_inetname(struct sockaddr_in *from)
+{
+       const char *ina;
+
+       ina = inet_ntoa(from->sin_addr);
+       if (nflag)
+               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 inline void
+print(unsigned char *buf, int cc, struct sockaddr_in *from)
+{
+       struct ip *ip;
+       int hlen;
+
+       ip = (struct ip *) buf;
+       hlen = ip->ip_hl << 2;
+       cc -= hlen;
+
+       print_inetname(from);
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+       if (verbose)
+               printf(" %d bytes to %s", cc, inet_ntoa(ip->ip_dst));
+#endif
+}
+
+
+static struct hostinfo *
+gethostinfo(const char *host)
+{
+       int n;
+       struct hostent *hp;
+       struct hostinfo *hi;
+       char **p;
+       uint32_t addr, *ap;
+
+       hi = xzalloc(sizeof(*hi));
+       addr = inet_addr(host);
+       if (addr != 0xffffffff) {
+               hi->name = xstrdup(host);
+               hi->n = 1;
+               hi->addrs = xzalloc(sizeof(hi->addrs[0]));
+               hi->addrs[0] = addr;
+               return hi;
+       }
+
+       hp = xgethostbyname(host);
+       if (hp->h_addrtype != AF_INET || hp->h_length != 4)
+               bb_perror_msg_and_die("bad host %s", host);
+       hi->name = xstrdup(hp->h_name);
+       for (n = 0, p = hp->h_addr_list; *p != NULL; ++n, ++p)
+               continue;
+       hi->n = n;
+       hi->addrs = xzalloc(n * sizeof(hi->addrs[0]));
+       for (ap = hi->addrs, p = hp->h_addr_list; *p != NULL; ++ap, ++p)
+               memcpy(ap, *p, sizeof(*ap));
+       return hi;
+}
+
+static void
+freehostinfo(struct hostinfo *hi)
+{
+       free(hi->name);
+       free(hi->addrs);
+       free(hi);
+}
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+static void
+getaddr(uint32_t *ap, const char *host)
+{
+       struct hostinfo *hi;
+
+       hi = gethostinfo(host);
+       *ap = hi->addrs[0];
+       freehostinfo(hi);
+}
+#endif
+
+static void
+print_delta_ms(unsigned t1p, unsigned t2p)
+{
+       unsigned tt = t2p - t1p;
+       printf("  %u.%03u ms", tt/1000, tt%1000);
+}
+
+int traceroute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int traceroute_main(int argc, char **argv)
+{
+       int code, n;
+       unsigned char *outp;
+       uint32_t *ap;
+       struct sockaddr_in *from;
+       struct sockaddr_in *to;
+       struct hostinfo *hi;
+       int ttl, probe, i;
+       int seq = 0;
+       int tos = 0;
+       char *tos_str;
+       char *source;
+       unsigned op;
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       int lsrr = 0;
+#endif
+       uint16_t off = 0;
+       struct IFADDRLIST *al;
+       char *device;
+       int max_ttl = 30;
+       char *max_ttl_str;
+       char *port_str;
+       int nprobes = 3;
+       char *nprobes_str;
+       char *waittime_str;
+       unsigned pausemsecs = 0;
+       char *pausemsecs_str;
+       int first_ttl = 1;
+       char *first_ttl_str;
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       llist_t *source_route_list = NULL;
+#endif
+
+       INIT_G();
+       from = (struct sockaddr_in *)&wherefrom;
+       to = (struct sockaddr_in *)&whereto;
+
+       //opterr = 0;
+#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 (op & OPT_DONT_FRAGMNT)
+               off = IP_DF;
+       if (op & OPT_IP_CHKSUM) {
+               doipcksum = 0;
+               bb_error_msg("warning: ip checksums disabled");
+       }
+       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())
+                       bb_error_msg_and_die("-s %s: permission denied", source);
+       }
+       if (op & OPT_WAITTIME)
+               waittime = xatou_range(waittime_str, 2, 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, 255);
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+       if (source_route_list) {
+               llist_t *l_sr;
+
+               l_sr = source_route_list;
+               while (l_sr) {
+                       if (lsrr >= NGATEWAYS)
+                               bb_error_msg_and_die("no more than %d gateways", NGATEWAYS);
+                       getaddr(gwlist + lsrr, l_sr->data);
+                       ++lsrr;
+                       l_sr = l_sr->link;
+                       free(source_route_list);
+                       source_route_list = l_sr;
+               }
+               optlen = (lsrr + 1) * sizeof(gwlist[0]);
+       }
+#endif
+
+       if (first_ttl > max_ttl) {
+               bb_error_msg_and_die(
+                   "first ttl (%d) may not be greater than max ttl (%d)",
+                   first_ttl, max_ttl);
+       }
+
+       minpacket = sizeof(*outip) + sizeof(*outdata) + optlen;
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+       if (useicmp)
+               minpacket += 8;                 /* XXX magic number */
+       else
+#endif
+               minpacket += sizeof(*outudp);
+       packlen = minpacket;                    /* minimum sized packet */
+
+       /* Process destination and optional packet size */
+       switch (argc - optind) {
+
+       case 2:
+               packlen = xatoul_range(argv[optind + 1], minpacket, maxpacket);
+               /* Fall through */
+
+       case 1:
+               hostname = argv[optind];
+               hi = gethostinfo(hostname);
+               setsin(to, hi->addrs[0]);
+               if (hi->n > 1)
+                       bb_error_msg("warning: %s has multiple addresses; using %s",
+                               hostname, inet_ntoa(to->sin_addr));
+               hostname = hi->name;
+               hi->name = NULL;
+               freehostinfo(hi);
+               break;
+
+       default:
+               bb_show_usage();
+       }
+
+       /* Ensure the socket fds won't be 0, 1 or 2 */
+       bb_sanitize_stdio();
+
+       rcvsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+
+#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));
+
+       sndsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+#if defined(IP_OPTIONS)
+       if (lsrr > 0) {
+               unsigned char optlist[MAX_IPOPTLEN];
+
+               /* final hop */
+               gwlist[lsrr] = to->sin_addr.s_addr;
+               ++lsrr;
+
+               /* force 4 byte alignment */
+               optlist[0] = IPOPT_NOP;
+               /* loose source route option */
+               optlist[1] = IPOPT_LSRR;
+               i = lsrr * sizeof(gwlist[0]);
+               optlist[2] = i + 3;
+               /* Pointer to LSRR addresses */
+               optlist[3] = IPOPT_MINOFF;
+               memcpy(optlist + 4, gwlist, i);
+
+               if ((setsockopt(sndsock, IPPROTO_IP, IP_OPTIONS,
+                   (char *)optlist, i + sizeof(gwlist[0]))) < 0) {
+                       bb_perror_msg_and_die("IP_OPTIONS");
+               }
+       }
+#endif /* IP_OPTIONS */
+#endif /* CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE */
+
+#ifdef SO_SNDBUF
+       if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, &packlen, sizeof(packlen)) < 0) {
+               bb_perror_msg_and_die("SO_SNDBUF");
+       }
+#endif
+#ifdef IP_HDRINCL
+       if (setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, &const_int_1, sizeof(const_int_1)) < 0
+        && errno != ENOPROTOOPT
+       ) {
+               bb_perror_msg_and_die("IP_HDRINCL");
+       }
+#else
+#ifdef IP_TOS
+       if (tos_str && setsockopt(sndsock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) {
+               bb_perror_msg_and_die("setsockopt tos %d", tos);
+       }
+#endif
+#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));
+
+       /* Revert to non-privileged user after opening sockets */
+       xsetgid(getgid());
+       xsetuid(getuid());
+
+       outip = xzalloc(packlen);
+
+       outip->ip_v = IPVERSION;
+       if (tos_str)
+               outip->ip_tos = tos;
+       outip->ip_len = htons(packlen);
+       outip->ip_off = htons(off);
+       outp = (unsigned char *)(outip + 1);
+       outip->ip_dst = to->sin_addr;
+
+       outip->ip_hl = (outp - (unsigned char *)outip) >> 2;
+       ident = (getpid() & 0xffff) | 0x8000;
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+       if (useicmp) {
+               outip->ip_p = IPPROTO_ICMP;
+               outicmp = (struct icmp *)outp;
+               outicmp->icmp_type = ICMP_ECHO;
+               outicmp->icmp_id = htons(ident);
+               outdata = (outdata_t *)(outp + 8); /* XXX magic number */
+       } else
+#endif
+       {
+               outip->ip_p = IPPROTO_UDP;
+               outudp = (struct udphdr *)outp;
+               outudp->source = htons(ident);
+               outudp->len = htons((uint16_t)(packlen - (sizeof(*outip) + optlen)));
+               outdata = (outdata_t *)(outudp + 1);
+       }
+
+       /* Get the interface address list */
+       n = ifaddrlist(&al);
+
+       /* Look for a specific device */
+       if (op & OPT_DEVICE) {
+               for (i = n; i > 0; --i, ++al)
+                       if (strcmp(device, al->device) == 0)
+                               goto found_dev;
+               bb_error_msg_and_die("can't find interface %s", device);
+       }
+ found_dev:
+
+       /* Determine our source address */
+       if (!(op & OPT_SOURCE)) {
+               /*
+                * If a device was specified, use the interface address.
+                * Otherwise, try to determine our source address.
+                */
+               if (op & OPT_DEVICE)
+                       setsin(from, al->addr);
+               findsaddr(to, from);
+       } else {
+               hi = gethostinfo(source);
+               source = hi->name;
+               hi->name = NULL;
+               /*
+                * If the device was specified make sure it
+                * corresponds to the source address specified.
+                * Otherwise, use the first address (and warn if
+                * there are more than one).
+                */
+               if (op & OPT_DEVICE) {
+                       for (i = hi->n, ap = hi->addrs; i > 0; --i, ++ap)
+                               if (*ap == al->addr)
+                                       goto found_dev2;
+                       bb_error_msg_and_die("%s is not on interface %s",
+                                       source, device);
+ found_dev2:
+                       setsin(from, *ap);
+               } else {
+                       setsin(from, hi->addrs[0]);
+                       if (hi->n > 1)
+                               bb_error_msg(
+                       "warning: %s has multiple addresses; using %s",
+                                   source, inet_ntoa(from->sin_addr));
+               }
+               freehostinfo(hi);
+       }
+
+       outip->ip_src = from->sin_addr;
+#ifndef IP_HDRINCL
+       xbind(sndsock, (struct sockaddr *)from, sizeof(*from));
+#endif
+
+       printf("traceroute to %s (%s)", hostname, inet_ntoa(to->sin_addr));
+       if (op & OPT_SOURCE)
+               printf(" from %s", source);
+       printf(", %d hops max, %d byte packets\n", max_ttl, packlen);
+       fflush(stdout);
+
+       for (ttl = first_ttl; ttl <= max_ttl; ++ttl) {
+               uint32_t lastaddr = 0;
+               int gotlastaddr = 0;
+               int got_there = 0;
+               int unreachable = 0;
+               int sentfirst = 0;
+
+               printf("%2d ", ttl);
+               for (probe = 0; probe < nprobes; ++probe) {
+                       int cc;
+                       unsigned t1;
+                       unsigned t2;
+                       struct ip *ip;
+
+                       if (sentfirst && pausemsecs > 0)
+                               usleep(pausemsecs * 1000);
+                       t1 = monotonic_us();
+                       send_probe(++seq, ttl);
+                       ++sentfirst;
+                       while ((cc = wait_for_reply(rcvsock, from)) != 0) {
+                               t2 = monotonic_us();
+                               i = packet_ok(packet, cc, from, seq);
+                               /* Skip short packet */
+                               if (i == 0)
+                                       continue;
+                               if (!gotlastaddr ||
+                                   from->sin_addr.s_addr != lastaddr) {
+                                       print(packet, cc, from);
+                                       lastaddr = from->sin_addr.s_addr;
+                                       ++gotlastaddr;
+                               }
+                               print_delta_ms(t1, t2);
+                               ip = (struct ip *)packet;
+                               if (op & OPT_TTL_FLAG)
+                                       printf(" (%d)", ip->ip_ttl);
+                               if (i == -2) {
+                                       if (ip->ip_ttl <= 1)
+                                               printf(" !");
+                                       ++got_there;
+                                       break;
+                               }
+                               /* time exceeded in transit */
+                               if (i == -1)
+                                       break;
+                               code = i - 1;
+                               switch (code) {
+
+                               case ICMP_UNREACH_PORT:
+                                       if (ip->ip_ttl <= 1)
+                                               printf(" !");
+                                       ++got_there;
+                                       break;
+
+                               case ICMP_UNREACH_NET:
+                                       ++unreachable;
+                                       printf(" !N");
+                                       break;
+
+                               case ICMP_UNREACH_HOST:
+                                       ++unreachable;
+                                       printf(" !H");
+                                       break;
+
+                               case ICMP_UNREACH_PROTOCOL:
+                                       ++got_there;
+                                       printf(" !P");
+                                       break;
+
+                               case ICMP_UNREACH_NEEDFRAG:
+                                       ++unreachable;
+                                       printf(" !F-%d", pmtu);
+                                       break;
+
+                               case ICMP_UNREACH_SRCFAIL:
+                                       ++unreachable;
+                                       printf(" !S");
+                                       break;
+
+                               case ICMP_UNREACH_FILTER_PROHIB:
+                               case ICMP_UNREACH_NET_PROHIB:   /* misuse */
+                                       ++unreachable;
+                                       printf(" !A");
+                                       break;
+
+                               case ICMP_UNREACH_HOST_PROHIB:
+                                       ++unreachable;
+                                       printf(" !C");
+                                       break;
+
+                               case ICMP_UNREACH_HOST_PRECEDENCE:
+                                       ++unreachable;
+                                       printf(" !V");
+                                       break;
+
+                               case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+                                       ++unreachable;
+                                       printf(" !C");
+                                       break;
+
+                               case ICMP_UNREACH_NET_UNKNOWN:
+                               case ICMP_UNREACH_HOST_UNKNOWN:
+                                       ++unreachable;
+                                       printf(" !U");
+                                       break;
+
+                               case ICMP_UNREACH_ISOLATED:
+                                       ++unreachable;
+                                       printf(" !I");
+                                       break;
+
+                               case ICMP_UNREACH_TOSNET:
+                               case ICMP_UNREACH_TOSHOST:
+                                       ++unreachable;
+                                       printf(" !T");
+                                       break;
+
+                               default:
+                                       ++unreachable;
+                                       printf(" !<%d>", code);
+                                       break;
+                               }
+                               break;
+                       }
+                       if (cc == 0)
+                               printf(" *");
+                       (void)fflush(stdout);
+               }
+               bb_putchar('\n');
+               if (got_there ||
+                   (unreachable > 0 && unreachable >= nprobes - 1))
+                       break;
+       }
+       return 0;
+}
diff --git a/networking/udhcp/Config.in b/networking/udhcp/Config.in
new file mode 100644 (file)
index 0000000..bbc1220
--- /dev/null
@@ -0,0 +1,133 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+config APP_UDHCPD
+       bool "udhcp Server (udhcpd)"
+       default n
+       help
+         uDHCPd is a DHCP server geared primarily toward embedded systems,
+         while striving to be fully functional and RFC compliant.
+
+         See http://udhcp.busybox.net for further details.
+
+config APP_DHCPRELAY
+       bool "dhcprelay"
+       default n
+       depends on APP_UDHCPD
+       help
+         dhcprelay listens for dhcp requests on one or more interfaces
+         and forwards these requests to a different interface or dhcp
+         server.
+
+config APP_DUMPLEASES
+       bool "Lease display utility (dumpleases)"
+       default n
+       depends on APP_UDHCPD
+       help
+         dumpleases displays the leases written out by the udhcpd server.
+         Lease times are stored in the file by time remaining in lease, or
+         by the absolute time that it expires in seconds from epoch.
+
+         See http://udhcp.busybox.net for further details.
+
+config 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 eleminating 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
+         The udhcpd stores address in lease files. Normaly it is save
+         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
+         notifies a set of scripts when a lease is obtained or lost.
+
+         See http://udhcp.busybox.net for further details.
+
+config FEATURE_UDHCPC_ARPING
+       bool "Ask udhcpc to verify that the offered address is free, using arpping"
+       default y
+       depends on APP_UDHCPC
+       help
+         If selected, udhcpc will use arpping to make sure the offered address
+         is really available. 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 FEATURE_UDHCP_DEBUG
+       bool "Compile udhcp with noisy debugging messages"
+       default n
+       depends on APP_UDHCPD || APP_UDHCPC
+       help
+         If selected, udhcpd will output extra debugging output.  If using
+         this option, compile uDHCP with "-g", and do not fork the daemon to
+         the background.
+
+         See http://udhcp.busybox.net for further details.
+
+config FEATURE_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 DHCPC_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 and answer. See
+         examples/udhcp for a working example. Normaly it is save
+         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 will 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 options 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 big.
diff --git a/networking/udhcp/Kbuild b/networking/udhcp/Kbuild
new file mode 100644 (file)
index 0000000..f4be6df
--- /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_RFC3397)   += domain_codec.o
diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c
new file mode 100644 (file)
index 0000000..45597c0
--- /dev/null
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * arpping.c
+ *
+ * Mostly stolen from: dhcpcd - DHCP client daemon
+ * by Yoichi Hariguchi <yoichi@fore.com>
+ */
+
+#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) */
+} ATTRIBUTE_PACKED;
+
+enum {
+       ARP_MSG_SIZE = 0x2a
+};
+
+
+/* Returns 1 if no reply received */
+
+int 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 -= (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..29d0d9a
--- /dev/null
@@ -0,0 +1,239 @@
+/* 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 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;
+
+       packet->options[end + OPT_CODE] = DHCP_PARAM_REQ;
+       for (i = 0; (c = dhcp_options[i].code) != 0; i++) {
+               if ((dhcp_options[i].flags & OPTION_REQ)
+                || (client_config.opt_mask[c >> 3] & (1 << (c & 7)))
+               ) {
+                       packet->options[end + OPT_DATA + len] = c;
+                       len++;
+               }
+       }
+       packet->options[end + OPT_LEN] = len;
+       packet->options[end + OPT_DATA + len] = DHCP_END;
+}
+
+
+#if ENABLE_FEATURE_UDHCPC_ARPING
+/* Unicast a DHCP decline message */
+int 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 udhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST,
+               SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex);
+}
+#endif
+
+/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */
+int 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 udhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST,
+                       SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex);
+}
+
+
+/* Broadcasts a DHCP request message */
+int send_selecting(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 udhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST,
+                               SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex);
+}
+
+
+/* Unicasts or broadcasts a DHCP renew message */
+int send_renew(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 udhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST,
+                               SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex);
+}
+
+
+/* Unicasts a DHCP release message */
+int 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 get_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");
+               sleep(1); /* possible down interface, looping condition */
+               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..1142001
--- /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 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, (~(uint32_t)0) ),               /* 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..d44bca4
--- /dev/null
@@ -0,0 +1,101 @@
+/* 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 _COMMON_H
+#define _COMMON_H
+
+#include "libbb.h"
+
+#define DEFAULT_SCRIPT   CONFIG_DHCPC_DEFAULT_SCRIPT
+
+extern const uint8_t MAC_BCAST_ADDR[6]; /* six all-ones */
+
+/*** packet.h ***/
+
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+
+#define DHCP_OPTIONS_BUFSIZE  308
+
+struct dhcpMessage {
+       uint8_t op;
+       uint8_t htype;
+       uint8_t hlen;
+       uint8_t hops;
+       uint32_t xid;
+       uint16_t secs;
+       uint16_t flags;
+       uint32_t ciaddr;
+       uint32_t yiaddr;
+       uint32_t siaddr;
+       uint32_t giaddr;
+       uint8_t chaddr[16];
+       uint8_t sname[64];
+       uint8_t file[128];
+       uint32_t cookie;
+       uint8_t options[DHCP_OPTIONS_BUFSIZE + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS];
+} ATTRIBUTE_PACKED;
+
+struct udp_dhcp_packet {
+       struct iphdr ip;
+       struct udphdr udp;
+       struct dhcpMessage data;
+} ATTRIBUTE_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);
+
+void udhcp_init_header(struct dhcpMessage *packet, char type);
+
+int udhcp_recv_packet(struct dhcpMessage *packet, int fd);
+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);
+int udhcp_send_kernel_packet(struct dhcpMessage *payload,
+               uint32_t source_ip, int source_port,
+               uint32_t dest_ip, int dest_port);
+
+
+/**/
+
+void udhcp_run_script(struct dhcpMessage *packet, const char *name);
+
+// 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
+/* from socket.h */
+#define listen_socket          udhcp_listen_socket
+#define read_interface         udhcp_read_interface
+
+void udhcp_sp_setup(void);
+int udhcp_sp_fd_set(fd_set *rfds, int extra_fd);
+int udhcp_sp_read(const fd_set *rfds);
+int raw_socket(int ifindex);
+int read_interface(const char *interface, int *ifindex, uint32_t *addr, uint8_t *arp);
+int listen_socket(/*uint32_t ip,*/ int port, const char *inf);
+/* Returns 1 if no reply received */
+int arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface);
+
+#if ENABLE_FEATURE_UDHCP_DEBUG
+# define DEBUG(str, args...) bb_info_msg("### " str, ## args)
+#else
+# define DEBUG(str, args...) do {;} while (0)
+#endif
+
+#endif
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
new file mode 100644 (file)
index 0000000..fe8f4c8
--- /dev/null
@@ -0,0 +1,631 @@
+/* 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 <getopt.h>
+#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 timeout; /* = 0. Must be signed */
+static uint32_t requested_ip; /* = 0 */
+static uint32_t server_addr;
+static int sockfd = -1;
+
+#define LISTEN_NONE 0
+#define LISTEN_KERNEL 1
+#define LISTEN_RAW 2
+static smallint listen_mode;
+
+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");
+       if (sockfd >= 0) {
+               close(sockfd);
+               sockfd = -1;
+       }
+       listen_mode = new_mode;
+}
+
+
+/* perform a renew */
+static void perform_renew(void)
+{
+       bb_info_msg("Performing a DHCP renew");
+       switch (state) {
+       case BOUND:
+               change_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(void)
+{
+       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;
+       timeout = INT_MAX;
+}
+
+
+static void client_background(void)
+{
+#if !BB_MMU
+       bb_error_msg("cannot background in uclinux (yet)");
+/* ... mainly because udhcpc calls client_background()
+ * in _the _middle _of _udhcpc _run_, not at the start!
+ * If that will be properly disabled for NOMMU, client_background()
+ * will work on NOMMU too */
+#else
+       bb_daemonize(0);
+       logmode &= ~LOGMODE_STDIO;
+       /* rewrite pidfile, as our pid is different now */
+       write_pidfile(client_config.pidfile);
+#endif
+       /* Do not fork again. */
+       client_config.foreground = 1;
+       client_config.background_if_no_lease = 0;
+}
+
+
+static uint8_t* alloc_dhcp_option(int code, const char *str, int extra)
+{
+       uint8_t *storage;
+       int len = strlen(str);
+       if (len > 255) len = 255;
+       storage = xzalloc(len + extra + OPT_DATA);
+       storage[OPT_CODE] = code;
+       storage[OPT_LEN] = len + extra;
+       memcpy(storage + extra + OPT_DATA, str, len);
+       return storage;
+}
+
+
+int udhcpc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int udhcpc_main(int argc ATTRIBUTE_UNUSED, 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;
+#if ENABLE_FEATURE_UDHCPC_ARPING
+       char *str_W;
+#endif
+       int tryagain_timeout = 20;
+       int discover_timeout = 3;
+       int discover_retries = 3;
+       uint32_t xid = 0;
+       uint32_t lease_seconds = 0; /* can be given as 32-bit quantity */
+       int packet_num;
+       /* t1, t2... what a wonderful names... */
+       unsigned t1 = t1; /* for gcc */
+       unsigned t2 = t2;
+       unsigned timestamp_got_lease = timestamp_got_lease;
+       unsigned opt;
+       int max_fd;
+       int retval;
+       int len;
+       struct timeval tv;
+       struct in_addr temp_addr;
+       struct dhcpMessage packet;
+       fd_set rfds;
+
+       enum {
+               OPT_c = 1 << 0,
+               OPT_C = 1 << 1,
+               OPT_V = 1 << 2,
+               OPT_f = 1 << 3,
+               OPT_b = 1 << 4,
+               OPT_H = 1 << 5,
+               OPT_h = 1 << 6,
+               OPT_F = 1 << 7,
+               OPT_i = 1 << 8,
+               OPT_n = 1 << 9,
+               OPT_p = 1 << 10,
+               OPT_q = 1 << 11,
+               OPT_R = 1 << 12,
+               OPT_r = 1 << 13,
+               OPT_s = 1 << 14,
+               OPT_T = 1 << 15,
+               OPT_t = 1 << 16,
+               OPT_v = 1 << 17,
+               OPT_S = 1 << 18,
+               OPT_A = 1 << 19,
+#if ENABLE_FEATURE_UDHCPC_ARPING
+               OPT_a = 1 << 20,
+               OPT_W = 1 << 21,
+#endif
+               OPT_P = 1 << 22,
+       };
+#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"
+               "foreground\0"     No_argument       "f"
+               "background\0"     No_argument       "b"
+               "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"
+#if ENABLE_FEATURE_UDHCPC_ARPING
+               "arping\0"         No_argument       "a"
+#endif
+               "request-option\0" Required_argument "O"
+#if ENABLE_FEATURE_UDHCP_PORT
+               "client-port\0"    Required_argument "P"
+#endif
+               ;
+#endif
+       /* Default options. */
+#if ENABLE_FEATURE_UDHCP_PORT
+       SERVER_PORT = 67;
+       CLIENT_PORT = 68;
+#endif
+       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+";
+#if ENABLE_GETOPT_LONG
+       applet_long_options = udhcpc_longopts;
+#endif
+       opt = getopt32(argv, "c:CV:fbH:h:F:i:np:qRr:s:T:t:vSA:"
+               USE_FEATURE_UDHCPC_ARPING("aW:")
+               USE_FEATURE_UDHCP_PORT("P:")
+               "O:"
+               , &str_c, &str_V, &str_h, &str_h, &str_F
+               , &client_config.interface, &client_config.pidfile, &str_r
+               , &client_config.script
+               , &discover_timeout, &discover_retries, &tryagain_timeout
+               USE_FEATURE_UDHCPC_ARPING(, &str_W)
+               USE_FEATURE_UDHCP_PORT(, &str_P)
+               , &list_O
+               );
+
+       if (opt & OPT_c)
+               client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0);
+       //if (opt & OPT_C)
+       if (opt & OPT_V)
+               client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0);
+       if (opt & OPT_f)
+               client_config.foreground = 1;
+       if (opt & OPT_b)
+               client_config.background_if_no_lease = 1;
+       if (opt & (OPT_h|OPT_H))
+               client_config.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0);
+       if (opt & OPT_F) {
+               client_config.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3);
+               /* Flags: 0000NEOS
+               S: 1 => Client requests Server to update A RR in DNS as well as PTR
+               O: 1 => Server indicates to client that DNS has been updated regardless
+               E: 1 => Name data is DNS format, i.e. <4>host<6>domain<4>com<0> not "host.domain.com"
+               N: 1 => Client requests Server to not update DNS
+               */
+               client_config.fqdn[OPT_DATA + 0] = 0x1;
+               /* client_config.fqdn[OPT_DATA + 1] = 0; - redundant */
+               /* client_config.fqdn[OPT_DATA + 2] = 0; - redundant */
+       }
+       // if (opt & OPT_i) client_config.interface = ...
+       if (opt & OPT_n)
+               client_config.abort_if_no_lease = 1;
+       // if (opt & OPT_p) client_config.pidfile = ...
+       if (opt & OPT_q)
+               client_config.quit_after_lease = 1;
+       if (opt & OPT_R)
+               client_config.release_on_quit = 1;
+       if (opt & OPT_r)
+               requested_ip = inet_addr(str_r);
+       // if (opt & OPT_s) client_config.script = ...
+       // if (opt & OPT_T) discover_timeout = xatoi_u(str_T);
+       // if (opt & OPT_t) discover_retries = xatoi_u(str_t);
+       // if (opt & OPT_A) tryagain_timeout = xatoi_u(str_A);
+       if (opt & OPT_v) {
+               puts("version "BB_VER);
+               return 0;
+       }
+       if (opt & OPT_S) {
+               openlog(applet_name, LOG_PID, LOG_LOCAL0);
+               logmode |= LOGMODE_SYSLOG;
+       }
+#if ENABLE_FEATURE_UDHCP_PORT
+       if (opt & OPT_P) {
+               CLIENT_PORT = xatou16(str_P);
+               SERVER_PORT = CLIENT_PORT - 1;
+       }
+#endif
+       while (list_O) {
+               int n = index_in_strings(dhcp_option_strings, list_O->data);
+               if (n < 0)
+                       bb_error_msg_and_die("unknown option '%s'", list_O->data);
+               n = dhcp_options[n].code;
+               client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+               list_O = list_O->link;
+       }
+
+       if (read_interface(client_config.interface, &client_config.ifindex,
+                          NULL, client_config.arp))
+               return 1;
+
+       /* 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);
+       /* if (!..) bb_perror_msg("cannot create pidfile %s", pidfile); */
+
+       /* Goes to stdout 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;
+
+       /* 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 (;;) {
+               tv.tv_sec = timeout;
+               tv.tv_usec = 0;
+
+               if (listen_mode != LISTEN_NONE && sockfd < 0) {
+                       if (listen_mode == LISTEN_KERNEL)
+                               sockfd = listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface);
+                       else
+                               sockfd = raw_socket(client_config.ifindex);
+               }
+               max_fd = udhcp_sp_fd_set(&rfds, sockfd);
+
+               retval = 0; /* If we already timed out, fall through, else... */
+               if (tv.tv_sec > 0) {
+                       DEBUG("Waiting on select...");
+                       retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);
+               }
+
+               if (retval < 0) {
+                       /* EINTR? signal was caught, don't panic */
+                       if (errno != EINTR) {
+                               /* Else: an error occured, panic! */
+                               bb_perror_msg_and_die("select");
+                       }
+                       continue;
+               }
+
+               /* If timeout dropped to zero, time to become active:
+                * resend discover/renew/whatever
+                */
+               if (retval == 0) {
+                       switch (state) {
+                       case INIT_SELECTING:
+                               if (packet_num < discover_retries) {
+                                       if (packet_num == 0)
+                                               xid = random_xid();
+
+                                       /* send discover packet */
+                                       send_discover(xid, requested_ip); /* broadcast */
+
+                                       timeout = discover_timeout;
+                                       packet_num++;
+                                       continue;
+                               }
+                               udhcp_run_script(NULL, "leasefail");
+                               if (client_config.background_if_no_lease) {
+                                       bb_info_msg("No lease, forking to background");
+                                       client_background();
+                               } else if (client_config.abort_if_no_lease) {
+                                       bb_info_msg("No lease, failing");
+                                       retval = 1;
+                                       goto ret;
+                               }
+                               /* wait to try 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)
+                                               send_renew(xid, server_addr, requested_ip); /* unicast */
+                                       else send_selecting(xid, server_addr, requested_ip); /* broadcast */
+
+                                       timeout = ((packet_num == 2) ? 10 : 2);
+                                       packet_num++;
+                                       continue;
+                               }
+                               /* timed out, go back to init state */
+                               if (state == RENEW_REQUESTED)
+                                       udhcp_run_script(NULL, "deconfig");
+                               change_listen_mode(LISTEN_RAW);
+                               state = INIT_SELECTING;
+                               timeout = 0;
+                               packet_num = 0;
+                               continue;
+                       case BOUND:
+                               /* Lease is starting to run out, time to enter renewing state */
+                               change_listen_mode(LISTEN_KERNEL);
+                               DEBUG("Entering renew state");
+                               state = RENEWING;
+                               /* fall right through */
+                       case RENEWING:
+                               /* Either set a new T1, or enter REBINDING state */
+                               if ((t2 - t1) > (lease_seconds / (4*60*60) + 1)) {
+                                       /* send a request packet */
+                                       send_renew(xid, server_addr, requested_ip); /* unicast */
+                                       t1 += (t2 - t1) / 2;
+                                       timeout = t1 - ((int)monotonic_sec() - timestamp_got_lease);
+                                       continue;
+                               }
+                               /* Timed out, enter rebinding state */
+                               DEBUG("Entering rebinding state");
+                               state = REBINDING;
+                               timeout = (t2 - t1);
+                               continue;
+                       case REBINDING:
+                               /* Lease is *really* about to run out,
+                                * try to find DHCP server using broadcast */
+                               if ((lease_seconds - t2) > (lease_seconds / (4*60*60) + 1)) {
+                                       /* send a request packet */
+                                       send_renew(xid, 0, requested_ip); /* broadcast */
+                                       t2 += (lease_seconds - t2) / 2;
+                                       timeout = t2 - ((int)monotonic_sec() - timestamp_got_lease);
+                                       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;
+                               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 is a packet? */
+               if (listen_mode != LISTEN_NONE && FD_ISSET(sockfd, &rfds)) {
+                       /* A packet is ready, read it */
+
+                       if (listen_mode == LISTEN_KERNEL)
+                               len = udhcp_recv_packet(&packet, sockfd);
+                       else
+                               len = get_raw_packet(&packet, sockfd);
+
+                       if (len == -1) { /* error is severe, reopen socket */
+                               DEBUG("error on read, %s, reopening socket", strerror(errno));
+                               change_listen_mode(listen_mode); /* just close and reopen */
+                       }
+                       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 */
+                                       }
+                                       /* can be misaligned, thus memcpy */
+                                       memcpy(&server_addr, temp, 4);
+                                       xid = packet.xid;
+                                       requested_ip = packet.yiaddr;
+
+                                       /* enter requesting state */
+                                       state = REQUESTING;
+                                       timeout = 0;
+                                       packet_num = 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 {
+                                               /* can be misaligned, thus memcpy */
+                                               memcpy(&lease_seconds, temp, 4);
+                                               lease_seconds = ntohl(lease_seconds);
+                                       }
+#if ENABLE_FEATURE_UDHCPC_ARPING
+                                       if (opt & OPT_a) {
+                                               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;
+                                                       continue; /* back to main loop */
+                                               }
+                                       }
+#endif
+                                       /* enter bound state */
+                                       t1 = lease_seconds / 2;
+
+                                       /* little fixed point for n * .875 */
+                                       t2 = (lease_seconds * 7) >> 3;
+                                       temp_addr.s_addr = packet.yiaddr;
+                                       bb_info_msg("Lease of %s obtained, lease time %u",
+                                               inet_ntoa(temp_addr), (unsigned)lease_seconds);
+                                       timestamp_got_lease = monotonic_sec();
+                                       timeout = t1;
+                                       requested_ip = packet.yiaddr;
+                                       udhcp_run_script(&packet,
+                                                  ((state == RENEWING || state == REBINDING) ? "renew" : "bound"));
+
+                                       state = BOUND;
+                                       change_listen_mode(LISTEN_NONE);
+                                       if (client_config.quit_after_lease) {
+                                               if (client_config.release_on_quit)
+                                                       perform_release();
+                                               goto ret0;
+                                       }
+                                       if (!client_config.foreground)
+                                               client_background();
+
+                                       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;
+                               }
+                               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();
+                               break;
+                       case SIGTERM:
+                               bb_info_msg("Received SIGTERM");
+                               if (client_config.release_on_quit)
+                                       perform_release();
+                               goto ret0;
+                       }
+               }
+       } /* for (;;) - main loop ends */
+
+ ret0:
+       retval = 0;
+ ret:
+       /*if (client_config.pidfile) - remove_pidfile has it's 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..d0fde73
--- /dev/null
@@ -0,0 +1,63 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpc.h */
+
+#ifndef _DHCPC_H
+#define _DHCPC_H
+
+#define INIT_SELECTING 0
+#define REQUESTING     1
+#define BOUND          2
+#define RENEWING       3
+#define REBINDING      4
+#define INIT_REBOOT    5
+#define RENEW_REQUESTED 6
+#define RELEASED       7
+
+struct client_config_t {
+       /* TODO: combine flag fields into single "unsigned opt" */
+       /* (can be set directly to the result of getopt32) */
+       char foreground;                /* Do not fork */
+       char quit_after_lease;          /* Quit after obtaining lease */
+       char release_on_quit;           /* Perform release on quit */
+       char abort_if_no_lease;         /* Abort if no lease */
+       char background_if_no_lease;    /* Fork to background if no lease */
+       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 */
+       int ifindex;                    /* Index number of the interface to use */
+#if ENABLE_FEATURE_UDHCP_PORT
+       uint16_t port;
+#endif
+       uint8_t arp[6];                 /* Our arp address */
+       uint8_t opt_mask[256 / 8];      /* Bitmask of options to send (-O option) */
+};
+
+/* 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);
+int send_discover(uint32_t xid, uint32_t requested);
+int send_selecting(uint32_t xid, uint32_t server, uint32_t requested);
+#if ENABLE_FEATURE_UDHCPC_ARPING
+int send_decline(uint32_t xid, uint32_t server, uint32_t requested);
+#endif
+int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr);
+int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr);
+int send_release(uint32_t server, uint32_t ciaddr);
+int get_raw_packet(struct dhcpMessage *payload, int fd);
+
+
+#endif
diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c
new file mode 100644 (file)
index 0000000..2637196
--- /dev/null
@@ -0,0 +1,273 @@
+/* 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       fd_set rfds;
+       struct timeval tv;
+       int server_socket = -1, bytes, retval, max_sock;
+       struct dhcpMessage packet;
+       uint8_t *state, *server_id, *requested;
+       uint32_t server_id_align, requested_align, static_lease_ip;
+       unsigned 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_STDIO;
+       }
+
+       if (opt & 2) { /* -S */
+               openlog(applet_name, LOG_PID, LOG_LOCAL0);
+               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) {
+               memcpy(&server_config.lease, option->data + 2, 4);
+               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 (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 */
+
+               if (server_socket < 0) {
+                       server_socket = 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: break;          /* no signal */
+               default: continue;      /* signal or error (probably EINTR) */
+               }
+
+               bytes = udhcp_recv_packet(&packet, server_socket); /* this waits for a packet - idle */
+               if (bytes < 0) {
+                       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 (sendOffer(&packet) < 0) {
+                               bb_error_msg("send OFFER failed");
+                       }
+                       break;
+               case DHCPREQUEST:
+                       DEBUG("received REQUEST");
+
+                       requested = get_option(&packet, DHCP_REQUESTED_IP);
+                       server_id = get_option(&packet, DHCP_SERVER_ID);
+
+                       if (requested) memcpy(&requested_align, requested, 4);
+                       if (server_id) memcpy(&server_id_align, server_id, 4);
+
+                       if (lease) {
+                               if (server_id) {
+                                       /* SELECTING State */
+                                       DEBUG("server_id = %08x", ntohl(server_id_align));
+                                       if (server_id_align == server_config.server && requested
+                                        && requested_align == lease->yiaddr
+                                       ) {
+                                               sendACK(&packet, lease->yiaddr);
+                                       }
+                               } else if (requested) {
+                                       /* INIT-REBOOT State */
+                                       if (lease->yiaddr == requested_align)
+                                               sendACK(&packet, lease->yiaddr);
+                                       else
+                                               sendNAK(&packet);
+                               } else if (lease->yiaddr == packet.ciaddr) {
+                                       /* RENEWING or REBINDING State */
+                                       sendACK(&packet, lease->yiaddr);
+                               } else {
+                                       /* don't know what to do!!!! */
+                                       sendNAK(&packet);
+                               }
+
+                       /* what to do if we have no record of the client */
+                       } else if (server_id) {
+                               /* SELECTING State */
+
+                       } else if (requested) {
+                               /* INIT-REBOOT State */
+                               lease = find_lease_by_yiaddr(requested_align);
+                               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
+                                               sendNAK(&packet);
+                               } else {
+                                       uint32_t r = ntohl(requested_align);
+                                       if (r < server_config.start_ip
+                                        || r > server_config.end_ip
+                                       ) {
+                                               sendNAK(&packet);
+                                       }
+                                       /* else remain silent */
+                               }
+
+                       } else {
+                               /* RENEWING or REBINDING State */
+                       }
+                       break;
+               case DHCPDECLINE:
+                       DEBUG("Received DECLINE");
+                       if (lease) {
+                               memset(lease->chaddr, 0, 16);
+                               lease->expires = time(0) + server_config.decline_time;
+                       }
+                       break;
+               case DHCPRELEASE:
+                       DEBUG("Received RELEASE");
+                       if (lease)
+                               lease->expires = time(0);
+                       break;
+               case DHCPINFORM:
+                       DEBUG("Received INFORM");
+                       send_inform(&packet);
+                       break;
+               default:
+                       bb_info_msg("Unsupported DHCP message (%02x) - ignoring", state[0]);
+               }
+       }
+ 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..4ce442b
--- /dev/null
@@ -0,0 +1,117 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpd.h */
+
+#ifndef _DHCPD_H
+#define _DHCPD_H
+
+/************************************/
+/* Defaults _you_ may want to tweak */
+/************************************/
+
+/* the period of time the client is allowed to use that address */
+#define LEASE_TIME              (60*60*24*10) /* 10 days of seconds */
+#define LEASES_FILE            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;
+       uint8_t *mac;
+       uint32_t *ip;
+};
+
+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 */
+       char remaining;                 /* should the lease file be interpreted as lease time remaining, or
+                                        * as the 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 a client can request */
+       char *lease_file;
+       char *pidfile;
+       char *notify_file;              /* What to run whenever leases are written */
+       uint32_t siaddr;                /* next server bootp option */
+       char *sname;                    /* bootp server name */
+       char *boot_file;                /* bootp boot file option */
+       struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */
+};
+
+#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
+
+extern struct dhcpOfferedAddr *leases;
+
+
+/*** leases.h ***/
+
+struct dhcpOfferedAddr {
+       uint8_t chaddr[16];
+       uint32_t yiaddr;        /* network order */
+       uint32_t expires;       /* host order */
+};
+
+struct dhcpOfferedAddr *add_lease(const uint8_t *chaddr, uint32_t yiaddr, unsigned long lease);
+int lease_expired(struct dhcpOfferedAddr *lease);
+struct dhcpOfferedAddr *find_lease_by_chaddr(const uint8_t *chaddr);
+struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr);
+uint32_t find_address(int check_expired);
+
+
+/*** static_leases.h ***/
+
+/* Config file will pass static lease info to this function which will add it
+ * to a data structure that can be searched later */
+int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip);
+/* Check to see if a mac has an associated static lease */
+uint32_t getIpByMac(struct static_lease *lease_struct, void *arg);
+/* Check to see if an ip is reserved as a static ip */
+uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip);
+/* Print out static leases just to check what's going on (debug code) */
+void printStaticLeases(struct static_lease **lease_struct);
+
+
+/*** serverpacket.h ***/
+
+int sendOffer(struct dhcpMessage *oldpacket);
+int sendNAK(struct dhcpMessage *oldpacket);
+int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr);
+int send_inform(struct dhcpMessage *oldpacket);
+
+
+/*** files.h ***/
+
+void read_config(const char *file);
+void write_leases(void);
+void read_leases(const char *file);
+struct option_set *find_option(struct option_set *opt_list, uint8_t code);
+
+
+#endif
diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c
new file mode 100644 (file)
index 0000000..def1bc2
--- /dev/null
@@ -0,0 +1,314 @@
+/* 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"
+
+/* constants */
+#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) and returns numerically max fd. */
+static int init_sockets(char **client, int num_clients,
+                       char *server, int *fds)
+{
+       int i, n;
+
+       /* talk to real server on bootps */
+       fds[0] = listen_socket(/*INADDR_ANY,*/ SERVER_PORT, server);
+       n = fds[0];
+
+       for (i = 1; i < num_clients; i++) {
+               /* listen for clients on bootps */
+               fds[i] = listen_socket(/*INADDR_ANY,*/ SERVER_PORT, client[i-1]);
+               if (fds[i] > n)
+                       n = fds[i];
+       }
+       return n;
+}
+
+
+/**
+ * pass_on() - forwards dhcp packets from client to server
+ * p - packet to send
+ * client - number of the client
+ */
+static void pass_on(struct dhcpMessage *p, int packet_len, int client, int *fds,
+                       struct sockaddr_in *client_addr, struct sockaddr_in *server_addr)
+{
+       int res, type;
+       struct xid_item *item;
+
+       /* check packet_type */
+       type = get_dhcp_packet_type(p);
+       if (type != DHCPDISCOVER && type != DHCPREQUEST
+        && type != DHCPDECLINE && type != DHCPRELEASE
+        && type != DHCPINFORM
+       ) {
+               return;
+       }
+
+       /* create new xid entry */
+       item = xid_add(p->xid, client_addr, client);
+
+       /* forward request to LAN (server) */
+       res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr,
+                       sizeof(struct sockaddr_in));
+       if (res != packet_len) {
+               bb_perror_msg("pass_on");
+               return;
+       }
+}
+
+/**
+ * pass_back() - forwards dhcp packets from server to client
+ * p - packet to send
+ */
+static void pass_back(struct dhcpMessage *p, int packet_len, int *fds)
+{
+       int res, type;
+       struct xid_item *item;
+
+       /* check xid */
+       item = xid_find(p->xid);
+       if (!item) {
+               return;
+       }
+
+       /* check packet type */
+       type = get_dhcp_packet_type(p);
+       if (type != DHCPOFFER && type != DHCPACK && type != DHCPNAK) {
+               return;
+       }
+
+       if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY))
+               item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+       res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*)(&item->ip),
+                               sizeof(item->ip));
+       if (res != packet_len) {
+               bb_perror_msg("pass_back");
+               return;
+       }
+
+       /* remove xid entry */
+       xid_del(p->xid);
+}
+
+static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients,
+               struct sockaddr_in *server_addr, uint32_t gw_ip) ATTRIBUTE_NORETURN;
+static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients,
+               struct sockaddr_in *server_addr, uint32_t gw_ip)
+{
+       struct dhcpMessage dhcp_msg;
+       fd_set rfds;
+       size_t packlen;
+       socklen_t addr_size;
+       struct sockaddr_in client_addr;
+       struct timeval tv;
+       int i;
+
+       while (1) {
+               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_packet(&dhcp_msg, fds[0]);
+                               if (packlen > 0) {
+                                       pass_back(&dhcp_msg, packlen, fds);
+                               }
+                       }
+                       for (i = 1; i < num_sockets; i++) {
+                               /* clients */
+                               if (!FD_ISSET(fds[i], &rfds))
+                                       continue;
+                               addr_size = sizeof(struct sockaddr_in);
+                               packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0,
+                                                       (struct sockaddr *)(&client_addr), &addr_size);
+                               if (packlen <= 0)
+                                       continue;
+                               if (read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL))
+                                       dhcp_msg.giaddr = gw_ip;
+                               pass_on(&dhcp_msg, packlen, i, fds, &client_addr, server_addr);
+                       }
+               }
+               xid_expire();
+       }
+}
+
+int dhcprelay_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dhcprelay_main(int argc, char **argv)
+{
+       int num_sockets, max_socket;
+       int *fds;
+       uint32_t gw_ip;
+       char **clients;
+       struct sockaddr_in server_addr;
+
+       server_addr.sin_family = AF_INET;
+       server_addr.sin_port = htons(SERVER_PORT);
+       if (argc == 4) {
+               if (!inet_aton(argv[3], &server_addr.sin_addr))
+                       bb_perror_msg_and_die("didn't grok server");
+       } else if (argc == 3) {
+               server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+       } else {
+               bb_show_usage();
+       }
+
+       clients = get_client_devices(argv[1], &num_sockets);
+       num_sockets++; /* for server socket at fds[0] */
+       fds = xmalloc(num_sockets * sizeof(fds[0]));
+       max_socket = init_sockets(clients, num_sockets, argv[2], fds);
+
+       if (read_interface(argv[2], NULL, &gw_ip, NULL))
+               return 1;
+
+       /* doesn't return */
+       dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip);
+       /* 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..239ae5b
--- /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_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 *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 *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_RFC3397 */
diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c
new file mode 100644 (file)
index 0000000..83b3841
--- /dev/null
@@ -0,0 +1,67 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+#include <getopt.h>
+
+#include "common.h"
+#include "dhcpd.h"
+
+int dumpleases_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dumpleases_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int fd;
+       int i;
+       unsigned opt;
+       time_t expires;
+       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      Expires %s\n", (opt & OPT_a) ? "at" : "in");
+       /*     "00:00:00:00:00:00 255.255.255.255 Wed Jun 30 21:49:08 1993" */
+       while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) {
+               printf(":%02x"+1, lease.chaddr[0]);
+               for (i = 1; i < 6; i++) {
+                       printf(":%02x", lease.chaddr[i]);
+               }
+               addr.s_addr = lease.yiaddr;
+               printf(" %-15s ", inet_ntoa(addr));
+               expires = ntohl(lease.expires);
+               if (!(opt & OPT_a)) { /* no -a */
+                       if (!expires)
+                               puts("expired");
+                       else {
+                               unsigned d, h, m;
+                               d = expires / (24*60*60); expires %= (24*60*60);
+                               h = expires / (60*60); expires %= (60*60);
+                               m = expires / 60; expires %= 60;
+                               if (d) printf("%u days ", d);
+                               printf("%02u:%02u:%02u\n", h, m, (unsigned)expires);
+                       }
+               } else /* -a */
+                       fputs(ctime(&expires), stdout);
+       }
+       /* close(fd); */
+
+       return 0;
+}
diff --git a/networking/udhcp/files.c b/networking/udhcp/files.c
new file mode 100644 (file)
index 0000000..6a93bd0
--- /dev/null
@@ -0,0 +1,435 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * files.c -- DHCP server file manipulation *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ */
+
+#include <netinet/ether.h>
+
+#include "common.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+/* 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)
+{
+       uint8_t *mac_bytes = arg;
+       struct ether_addr *temp_ether_addr;
+
+       temp_ether_addr = ether_aton(line);
+       if (temp_ether_addr == NULL)
+               return 0;
+       memcpy(mac_bytes, temp_ether_addr, 6);
+       return 1;
+}
+
+
+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 *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_RFC3397
+               if ((option->flags & TYPE_MASK) == OPTION_STR1035)
+                       /* reuse buffer and length for RFC1035-formatted string */
+                       buffer = 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_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_RFC3397
+               if ((option->flags & TYPE_MASK) == OPTION_STR1035)
+                       /* reuse buffer and length for RFC1035-formatted string */
+                       buffer = 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_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] __attribute__((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_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;
+       uint8_t *mac_bytes;
+       uint32_t *ip;
+
+       /* Allocate memory for addresses */
+       mac_bytes = xmalloc(sizeof(unsigned char) * 8);
+       ip = xmalloc(sizeof(uint32_t));
+
+       /* Read mac */
+       line = (char *) const_line;
+       mac_string = strtok(line, " \t");
+       read_mac(mac_string, mac_bytes);
+
+       /* Read ip */
+       ip_string = strtok(NULL, " \t");
+       read_ip(ip_string, ip);
+
+       addStaticLease(arg, mac_bytes, ip);
+
+       if (ENABLE_FEATURE_UDHCP_DEBUG) printStaticLeases(arg);
+
+       return 1;
+}
+
+
+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), ""},
+       /* ADDME: static lease */
+};
+enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 };
+
+
+/*
+ * Domain names may have 254 chars, and string options can be 254
+ * chars long. However, 80 bytes will be enough for most, and won't
+ * hog up memory. If you have a special application, change it
+ */
+#define READ_CONFIG_BUF_SIZE 80
+
+void read_config(const char *file)
+{
+       FILE *in;
+       char buffer[READ_CONFIG_BUF_SIZE], *token, *line;
+       int i, lineno;
+
+       for (i = 0; i < KWS_WITH_DEFAULTS; i++)
+               keywords[i].handler(keywords[i].def, keywords[i].var);
+
+       in = fopen_or_warn(file, "r");
+       if (!in)
+               return;
+
+       lineno = 0;
+       while (fgets(buffer, READ_CONFIG_BUF_SIZE, in)) {
+               lineno++;
+               /* *strchrnul(buffer, '\n') = '\0'; - trim() will do it */
+               *strchrnul(buffer, '#') = '\0';
+
+               token = strtok(buffer, " \t");
+               if (!token) continue;
+               line = strtok(NULL, "");
+               if (!line) continue;
+
+               trim(line); /* remove leading/trailing whitespace */
+
+               for (i = 0; i < ARRAY_SIZE(keywords); i++) {
+                       if (!strcasecmp(token, keywords[i].keyword)) {
+                               if (!keywords[i].handler(line, keywords[i].var)) {
+                                       bb_error_msg("can't parse line %d in %s at '%s'",
+                                                       lineno, file, line);
+                                       /* reset back to the default value */
+                                       keywords[i].handler(keywords[i].def, keywords[i].var);
+                               }
+                               break;
+                       }
+               }
+       }
+       fclose(in);
+
+       server_config.start_ip = ntohl(server_config.start_ip);
+       server_config.end_ip = ntohl(server_config.end_ip);
+}
+
+
+void write_leases(void)
+{
+       int fp;
+       unsigned i;
+       time_t curr = time(0);
+       unsigned long tmp_time;
+
+       fp = open_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC);
+       if (fp < 0) {
+               return;
+       }
+
+       for (i = 0; i < server_config.max_leases; i++) {
+               if (leases[i].yiaddr != 0) {
+
+                       /* screw with the time in the struct, for easier writing */
+                       tmp_time = leases[i].expires;
+
+                       if (server_config.remaining) {
+                               if (lease_expired(&(leases[i])))
+                                       leases[i].expires = 0;
+                               else leases[i].expires -= curr;
+                       } /* else stick with the time we got */
+                       leases[i].expires = htonl(leases[i].expires);
+                       // FIXME: error check??
+                       full_write(fp, &leases[i], sizeof(leases[i]));
+
+                       /* then restore it when done */
+                       leases[i].expires = tmp_time;
+               }
+       }
+       close(fp);
+
+       if (server_config.notify_file) {
+               char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file);
+               system(cmd);
+               free(cmd);
+       }
+}
+
+
+void read_leases(const char *file)
+{
+       int fp;
+       unsigned int i = 0;
+       struct dhcpOfferedAddr lease;
+
+       fp = open_or_warn(file, O_RDONLY);
+       if (fp < 0) {
+               return;
+       }
+
+       while (i < server_config.max_leases
+        && full_read(fp, &lease, sizeof(lease)) == sizeof(lease)
+       ) {
+               /* ADDME: is it a static lease */
+               uint32_t y = ntohl(lease.yiaddr);
+               if (y >= server_config.start_ip && y <= server_config.end_ip) {
+                       lease.expires = ntohl(lease.expires);
+                       if (!server_config.remaining)
+                               lease.expires -= time(0);
+                       if (!(add_lease(lease.chaddr, lease.yiaddr, lease.expires))) {
+                               bb_error_msg("too many leases while loading %s", file);
+                               break;
+                       }
+                       i++;
+               }
+       }
+       DEBUG("Read %d leases", i);
+       close(fp);
+}
diff --git a/networking/udhcp/leases.c b/networking/udhcp/leases.c
new file mode 100644 (file)
index 0000000..1745fee
--- /dev/null
@@ -0,0 +1,149 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * leases.c -- tools to manage DHCP leases
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ */
+
+#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 = NULL;
+// TODO: use monotonic_sec()
+       unsigned long oldest_lease = time(0);
+       unsigned i;
+
+       for (i = 0; i < server_config.max_leases; i++)
+               if (oldest_lease > leases[i].expires) {
+                       oldest_lease = leases[i].expires;
+                       oldest = &(leases[i]);
+               }
+       return oldest;
+}
+
+
+/* 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 *add_lease(const uint8_t *chaddr, uint32_t yiaddr, unsigned long lease)
+{
+       struct dhcpOfferedAddr *oldest;
+
+       /* clean out any old ones */
+       clear_lease(chaddr, yiaddr);
+
+       oldest = oldest_expired_lease();
+
+       if (oldest) {
+               memcpy(oldest->chaddr, chaddr, 16);
+               oldest->yiaddr = yiaddr;
+               oldest->expires = time(0) + lease;
+       }
+
+       return oldest;
+}
+
+
+/* true if a lease has expired */
+int lease_expired(struct dhcpOfferedAddr *lease)
+{
+       return (lease->expires < (unsigned long) time(0));
+}
+
+
+/* Find the first lease that matches chaddr, NULL if no match */
+struct dhcpOfferedAddr *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 *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);
+       return 0;
+}
+
+
+/* find an assignable address, if check_expired is true, we check all the expired leases as well.
+ * Maybe this should try expired leases by age... */
+uint32_t find_address(int check_expired)
+{
+       uint32_t addr, ret;
+       struct dhcpOfferedAddr *lease = NULL;
+
+       addr = server_config.start_ip; /* addr is in host order here */
+       for (; addr <= server_config.end_ip; addr++) {
+               /* ie, 192.168.55.0 */
+               if (!(addr & 0xFF))
+                       continue;
+               /* ie, 192.168.55.255 */
+               if ((addr & 0xFF) == 0xFF)
+                       continue;
+               /* Only do if it isn't assigned as a static lease */
+               ret = htonl(addr);
+               if (!reservedIp(server_config.static_leases, ret)) {
+                       /* lease is not taken */
+                       lease = find_lease_by_yiaddr(ret);
+                       /* no lease or it expired and we are checking for expired leases */
+                       if ((!lease || (check_expired && lease_expired(lease)))
+                        && nobody_responds_to_arp(ret) /* it isn't used on the network */
+                       ) {
+                               return ret;
+                       }
+               }
+       }
+       return 0;
+}
diff --git a/networking/udhcp/options.c b/networking/udhcp/options.c
new file mode 100644 (file)
index 0000000..12e5662
--- /dev/null
@@ -0,0 +1,234 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * options.c -- DHCP server option packet tools
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ */
+
+#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_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_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_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, not aligned). */
+uint8_t *get_option(struct dhcpMessage *packet, int code)
+{
+       int i, length;
+       uint8_t *optionptr;
+       int over = 0;
+       int curr = OPTION_FIELD;
+
+       optionptr = packet->options;
+       i = 0;
+       length = sizeof(packet->options);
+       while (1) {
+               if (i >= length) {
+                       bb_error_msg("bogus packet, option fields too long");
+                       return NULL;
+               }
+               if (optionptr[i + OPT_CODE] == code) {
+                       if (i + 1 + optionptr[i + OPT_LEN] >= length) {
+                               bb_error_msg("bogus packet, option fields too long");
+                               return NULL;
+                       }
+                       return optionptr + i + 2;
+               }
+               switch (optionptr[i + OPT_CODE]) {
+               case DHCP_PADDING:
+                       i++;
+                       break;
+               case DHCP_OPTION_OVER:
+                       if (i + 1 + optionptr[i + OPT_LEN] >= length) {
+                               bb_error_msg("bogus packet, option fields too long");
+                               return NULL;
+                       }
+                       over = optionptr[i + 3];
+                       i += optionptr[OPT_LEN] + 2;
+                       break;
+               case DHCP_END:
+                       if (curr == OPTION_FIELD && (over & FILE_FIELD)) {
+                               optionptr = packet->file;
+                               i = 0;
+                               length = sizeof(packet->file);
+                               curr = FILE_FIELD;
+                       } else if (curr == FILE_FIELD && (over & SNAME_FIELD)) {
+                               optionptr = packet->sname;
+                               i = 0;
+                               length = sizeof(packet->sname);
+                               curr = SNAME_FIELD;
+                       } else
+                               return NULL;
+                       break;
+               default:
+                       i += optionptr[OPT_LEN + i] + 2;
+               }
+       }
+       return NULL;
+}
+
+
+/* return the position of the 'end' option (no bounds checking) */
+int end_option(uint8_t *optionptr)
+{
+       int i = 0;
+
+       while (optionptr[i] != DHCP_END) {
+               if (optionptr[i] == DHCP_PADDING)
+                       i++;
+               else
+                       i += optionptr[i + OPT_LEN] + 2;
+       }
+       return i;
+}
+
+
+/* add an option string to the options (an option string contains an option code,
+ * length, then data) */
+int add_option_string(uint8_t *optionptr, uint8_t *string)
+{
+       int end = end_option(optionptr);
+
+       /* end position + string length + option code/length + end option */
+       if (end + string[OPT_LEN] + 2 + 1 >= 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 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);
+                       /* This memcpy is for processors which can't
+                        * handle a simple unaligned 32-bit assignment */
+                       memcpy(&option[OPT_DATA], &data, 4);
+                       return add_option_string(optionptr, option);
+               }
+       }
+
+       bb_error_msg("cannot add option 0x%02x", code);
+       return 0;
+}
diff --git a/networking/udhcp/options.h b/networking/udhcp/options.h
new file mode 100644 (file)
index 0000000..e9eeefb
--- /dev/null
@@ -0,0 +1,115 @@
+/* vi: set sw=4 ts=4: */
+/* options.h */
+#ifndef _OPTIONS_H
+#define _OPTIONS_H
+
+#define TYPE_MASK      0x0F
+
+enum {
+       OPTION_IP = 1,
+       OPTION_IP_PAIR,
+       OPTION_STRING,
+#if ENABLE_FEATURE_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_OVER       0x34
+#define DHCP_MESSAGE_TYPE      0x35
+#define DHCP_SERVER_ID         0x36
+#define DHCP_PARAM_REQ         0x37
+#define DHCP_MESSAGE           0x38
+#define DHCP_MAX_SIZE          0x39
+#define DHCP_T1                        0x3a
+#define DHCP_T2                        0x3b
+#define DHCP_VENDOR            0x3c
+#define DHCP_CLIENT_ID         0x3d
+#define DHCP_FQDN              0x51
+#define DHCP_END               0xFF
+
+
+#define BOOTREQUEST            1
+#define BOOTREPLY              2
+
+#define ETH_10MB               1
+#define ETH_10MB_LEN           6
+
+#define DHCPDISCOVER           1
+#define DHCPOFFER              2
+#define DHCPREQUEST            3
+#define DHCPDECLINE            4
+#define DHCPACK                        5
+#define DHCPNAK                        6
+#define DHCPRELEASE            7
+#define DHCPINFORM             8
+
+#define BROADCAST_FLAG         0x8000
+
+#define OPTION_FIELD           0
+#define FILE_FIELD             1
+#define SNAME_FIELD            2
+
+/* miscellaneous defines */
+#define OPT_CODE 0
+#define OPT_LEN 1
+#define OPT_DATA 2
+
+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);
+int end_option(uint8_t *optionptr);
+int add_option_string(uint8_t *optionptr, uint8_t *string);
+int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data);
+#if ENABLE_FEATURE_RFC3397
+char *dname_dec(const uint8_t *cstr, int clen, const char *pre);
+uint8_t *dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen);
+#endif
+
+#endif
diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c
new file mode 100644 (file)
index 0000000..fb6ef71
--- /dev/null
@@ -0,0 +1,237 @@
+/* vi: set sw=4 ts=4: */
+
+#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 udhcp_init_header(struct dhcpMessage *packet, char type)
+{
+       memset(packet, 0, sizeof(struct dhcpMessage));
+       packet->op = BOOTREQUEST;
+       switch (type) {
+       case DHCPOFFER:
+       case DHCPACK:
+       case DHCPNAK:
+               packet->op = BOOTREPLY;
+       }
+       packet->htype = ETH_10MB;
+       packet->hlen = ETH_10MB_LEN;
+       packet->cookie = htonl(DHCP_MAGIC);
+       packet->options[0] = DHCP_END;
+       add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type);
+}
+
+
+/* read a packet from socket fd, return -1 on read error, -2 on packet error */
+int udhcp_recv_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",
+                                               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", "MSFT 98");
+                               packet->flags |= htons(BROADCAST_FLAG);
+                       }
+#endif
+               }
+       }
+
+       return bytes;
+}
+
+
+uint16_t udhcp_checksum(void *addr, int count)
+{
+       /* Compute Internet Checksum for "count" bytes
+        *         beginning at location "addr".
+        */
+       int32_t sum = 0;
+       uint16_t *source = (uint16_t *) addr;
+
+       while (count > 1)  {
+               /*  This is the inner loop */
+               sum += *source++;
+               count -= 2;
+       }
+
+       /*  Add left-over byte, if any */
+       if (count > 0) {
+               /* Make sure that the left-over byte is added correctly both
+                * with little and big endian hosts */
+               uint16_t tmp = 0;
+               *(uint8_t*)&tmp = *(uint8_t*)source;
+               sum += tmp;
+       }
+       /*  Fold 32-bit sum to 16 bits */
+       while (sum >> 16)
+               sum = (sum & 0xffff) + (sum >> 16);
+
+       return ~sum;
+}
+
+
+/* Construct a ip/udp header for a packet, send packet */
+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)
+{
+       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 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..71f0333
--- /dev/null
@@ -0,0 +1,239 @@
+/* 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_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:
+                       memcpy(&val_u16, option, 2);
+                       dest += sprintf(dest, "%u", ntohs(val_u16));
+                       break;
+               case OPTION_S16:
+                       memcpy(&val_s16, option, 2);
+                       dest += sprintf(dest, "%d", ntohs(val_s16));
+                       break;
+               case OPTION_U32:
+                       memcpy(&val_u32, option, 4);
+                       dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32));
+                       break;
+               case OPTION_S32:
+                       memcpy(&val_s32, option, 4);
+                       dest += sprintf(dest, "%ld", (long) ntohl(val_s32));
+                       break;
+               case OPTION_STRING:
+                       memcpy(dest, option, len);
+                       dest[len] = '\0';
+                       return ret;      /* Short circuit this case */
+#if ENABLE_FEATURE_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, j;
+       char **envp;
+       char *var;
+       const char *opt_name;
+       uint8_t *temp;
+       char 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_OVER);
+               if (temp)
+                       over = *temp;
+               if (!(over & FILE_FIELD) && packet->file[0])
+                       num_options++;
+               if (!(over & SNAME_FIELD) && packet->sname[0])
+                       num_options++;
+       }
+
+       envp = xzalloc(sizeof(char *) * (num_options + 5));
+       j = 0;
+       envp[j++] = xasprintf("interface=%s", client_config.interface);
+       var = getenv("PATH");
+       if (var)
+               envp[j++] = xasprintf("PATH=%s", var);
+       var = getenv("HOME");
+       if (var)
+               envp[j++] = xasprintf("HOME=%s", var);
+
+       if (packet == NULL)
+               return envp;
+
+       envp[j] = xmalloc(sizeof("ip=255.255.255.255"));
+       sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr);
+
+       opt_name = dhcp_option_strings;
+       i = 0;
+       while (*opt_name) {
+               temp = get_option(packet, dhcp_options[i].code);
+               if (!temp)
+                       goto next;
+               envp[j++] = alloc_fill_opts(temp, &dhcp_options[i], opt_name);
+
+               /* Fill in a subnet bits option for things like /24 */
+               if (dhcp_options[i].code == DHCP_SUBNET) {
+                       uint32_t subnet;
+                       memcpy(&subnet, temp, 4);
+                       envp[j++] = xasprintf("mask=%d", mton(subnet));
+               }
+ next:
+               opt_name += strlen(opt_name) + 1;
+               i++;
+       }
+       if (packet->siaddr) {
+               envp[j] = xmalloc(sizeof("siaddr=255.255.255.255"));
+               sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr);
+       }
+       if (!(over & FILE_FIELD) && packet->file[0]) {
+               /* watch out for invalid packets */
+               packet->file[sizeof(packet->file) - 1] = '\0';
+               envp[j++] = xasprintf("boot_file=%s", packet->file);
+       }
+       if (!(over & SNAME_FIELD) && packet->sname[0]) {
+               /* watch out for invalid packets */
+               packet->sname[sizeof(packet->sname) - 1] = '\0';
+               envp[j++] = xasprintf("sname=%s", packet->sname);
+       }
+       return envp;
+}
+
+
+/* Call a script with a par file and env vars */
+void udhcp_run_script(struct dhcpMessage *packet, const char *name)
+{
+       int pid;
+       char **envp, **curr;
+
+       if (client_config.script == NULL)
+               return;
+
+       DEBUG("vfork'ing and execle'ing %s", client_config.script);
+
+       envp = fill_envp(packet);
+
+       /* call script */
+// can we use wait4pid(spawn(...)) here?
+       pid = vfork();
+       if (pid < 0) return;
+       if (pid == 0) {
+               /* close fd's? */
+               /* exec script */
+               execle(client_config.script, client_config.script,
+                      name, NULL, envp);
+               bb_perror_msg_and_die("script %s failed", client_config.script);
+       }
+       safe_waitpid(pid, NULL, 0);
+       for (curr = envp; *curr; curr++)
+               free(*curr);
+       free(envp);
+}
diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c
new file mode 100644 (file)
index 0000000..1dc7233
--- /dev/null
@@ -0,0 +1,267 @@
+/* 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 (ntohs(payload->flags) & BROADCAST_FLAG) {
+               DEBUG("broadcasting packet to client (requested)");
+               ciaddr = INADDR_BROADCAST;
+               chaddr = MAC_BCAST_ADDR;
+       } else {
+               DEBUG("unicasting packet to client yiaddr");
+               ciaddr = payload->yiaddr;
+               chaddr = payload->chaddr;
+       }
+       return udhcp_send_raw_packet(payload, server_config.server, SERVER_PORT,
+                       ciaddr, CLIENT_PORT, chaddr, server_config.ifindex);
+}
+
+
+/* send a dhcp packet, if force broadcast is set, the packet will be broadcast to the client */
+static int send_packet(struct dhcpMessage *payload, int force_broadcast)
+{
+       int ret;
+
+       if (payload->giaddr)
+               ret = send_packet_to_relay(payload);
+       else ret = send_packet_to_client(payload, force_broadcast);
+       return ret;
+}
+
+
+static void init_packet(struct dhcpMessage *packet, struct dhcpMessage *oldpacket, char type)
+{
+       udhcp_init_header(packet, type);
+       packet->xid = oldpacket->xid;
+       memcpy(packet->chaddr, oldpacket->chaddr, 16);
+       packet->flags = oldpacket->flags;
+       packet->giaddr = oldpacket->giaddr;
+       packet->ciaddr = oldpacket->ciaddr;
+       add_simple_option(packet->options, DHCP_SERVER_ID, server_config.server);
+}
+
+
+/* add in the bootp options */
+static void add_bootp_options(struct dhcpMessage *packet)
+{
+       packet->siaddr = server_config.siaddr;
+       if (server_config.sname)
+               strncpy((char*)packet->sname, server_config.sname, sizeof(packet->sname) - 1);
+       if (server_config.boot_file)
+               strncpy((char*)packet->file, server_config.boot_file, sizeof(packet->file) - 1);
+}
+
+
+/* send a DHCP OFFER to a DHCP DISCOVER */
+int sendOffer(struct dhcpMessage *oldpacket)
+{
+       struct dhcpMessage packet;
+       struct dhcpOfferedAddr *lease = NULL;
+       uint32_t req_align, lease_time_align = server_config.lease;
+       uint8_t *req, *lease_time;
+       struct option_set *curr;
+       struct in_addr addr;
+
+       uint32_t static_lease_ip;
+
+       init_packet(&packet, oldpacket, DHCPOFFER);
+
+       static_lease_ip = getIpByMac(server_config.static_leases, oldpacket->chaddr);
+
+       /* ADDME: if static, short circuit */
+       if (!static_lease_ip) {
+               /* the client is in our lease/offered table */
+               lease = find_lease_by_chaddr(oldpacket->chaddr);
+               if (lease) {
+                       if (!lease_expired(lease))
+                               lease_time_align = lease->expires - time(0);
+                       packet.yiaddr = lease->yiaddr;
+               /* Or the client has a requested ip */
+               } else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP))
+                /* Don't look here (ugly hackish thing to do) */
+                && memcpy(&req_align, req, 4)
+                /* and the ip is in the lease range */
+                && ntohl(req_align) >= server_config.start_ip
+                && ntohl(req_align) <= server_config.end_ip
+                && !static_lease_ip /* Check that its not a static lease */
+                /* and is not already taken/offered */
+                && (!(lease = find_lease_by_yiaddr(req_align))
+                       /* or its taken, but expired */ /* ADDME: or maybe in here */
+                       || lease_expired(lease))
+               ) {
+                       packet.yiaddr = req_align; /* FIXME: oh my, is there a host using this IP? */
+                       /* otherwise, find a free IP */
+               } else {
+                       /* Is it a static lease? (No, because find_address skips static lease) */
+                       packet.yiaddr = find_address(0);
+                       /* try for an expired lease */
+                       if (!packet.yiaddr)
+                               packet.yiaddr = find_address(1);
+               }
+
+               if (!packet.yiaddr) {
+                       bb_error_msg("no IP addresses to give - OFFER abandoned");
+                       return -1;
+               }
+               if (!add_lease(packet.chaddr, packet.yiaddr, server_config.offer_time)) {
+                       bb_error_msg("lease pool is full - OFFER abandoned");
+                       return -1;
+               }
+               lease_time = get_option(oldpacket, DHCP_LEASE_TIME);
+               if (lease_time) {
+                       memcpy(&lease_time_align, lease_time, 4);
+                       lease_time_align = ntohl(lease_time_align);
+                       if (lease_time_align > server_config.lease)
+                               lease_time_align = server_config.lease;
+               }
+
+               /* Make sure we aren't just using the lease time from the previous offer */
+               if (lease_time_align < server_config.min_lease)
+                       lease_time_align = server_config.lease;
+               /* ADDME: end of short circuit */
+       } else {
+               /* It is a static lease... use it */
+               packet.yiaddr = static_lease_ip;
+       }
+
+       add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align));
+
+       curr = server_config.options;
+       while (curr) {
+               if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
+                       add_option_string(packet.options, curr->data);
+               curr = curr->next;
+       }
+
+       add_bootp_options(&packet);
+
+       addr.s_addr = packet.yiaddr;
+       bb_info_msg("Sending OFFER of %s", inet_ntoa(addr));
+       return send_packet(&packet, 0);
+}
+
+
+int sendNAK(struct dhcpMessage *oldpacket)
+{
+       struct dhcpMessage packet;
+
+       init_packet(&packet, oldpacket, DHCPNAK);
+
+       DEBUG("Sending NAK");
+       return send_packet(&packet, 1);
+}
+
+
+int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr)
+{
+       struct dhcpMessage packet;
+       struct option_set *curr;
+       uint8_t *lease_time;
+       uint32_t lease_time_align = server_config.lease;
+       struct in_addr addr;
+
+       init_packet(&packet, oldpacket, DHCPACK);
+       packet.yiaddr = yiaddr;
+
+       lease_time = get_option(oldpacket, DHCP_LEASE_TIME);
+       if (lease_time) {
+               memcpy(&lease_time_align, lease_time, 4);
+               lease_time_align = ntohl(lease_time_align);
+               if (lease_time_align > server_config.lease)
+                       lease_time_align = server_config.lease;
+               else if (lease_time_align < server_config.min_lease)
+                       lease_time_align = server_config.lease;
+       }
+
+       add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align));
+
+       curr = server_config.options;
+       while (curr) {
+               if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
+                       add_option_string(packet.options, curr->data);
+               curr = curr->next;
+       }
+
+       add_bootp_options(&packet);
+
+       addr.s_addr = packet.yiaddr;
+       bb_info_msg("Sending ACK to %s", inet_ntoa(addr));
+
+       if (send_packet(&packet, 0) < 0)
+               return -1;
+
+       add_lease(packet.chaddr, packet.yiaddr, lease_time_align);
+       if (ENABLE_FEATURE_UDHCPD_WRITE_LEASES_EARLY) {
+               /* rewrite the file with leases at every new acceptance */
+               write_leases();
+       }
+
+       return 0;
+}
+
+
+int send_inform(struct dhcpMessage *oldpacket)
+{
+       struct dhcpMessage packet;
+       struct option_set *curr;
+
+       init_packet(&packet, oldpacket, DHCPACK);
+
+       curr = server_config.options;
+       while (curr) {
+               if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
+                       add_option_string(packet.options, curr->data);
+               curr = curr->next;
+       }
+
+       add_bootp_options(&packet);
+
+       return send_packet(&packet, 0);
+}
diff --git a/networking/udhcp/signalpipe.c b/networking/udhcp/signalpipe.c
new file mode 100644 (file)
index 0000000..1486b3b
--- /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 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 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 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..2cbd4aa
--- /dev/null
@@ -0,0 +1,112 @@
+/* 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 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(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
+       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("%s (our ip) = %s", ifr.ifr_name, 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 listen_socket(/*uint32_t ip,*/ int port, const char *inf)
+{
+       int fd;
+       struct ifreq interface;
+       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");
+
+       strncpy(interface.ifr_name, inf, IFNAMSIZ);
+       if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &interface, sizeof(interface)) == -1)
+               bb_perror_msg_and_die("SO_BINDTODEVICE");
+
+       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..aabfb81
--- /dev/null
@@ -0,0 +1,99 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * static_leases.c -- Couple of functions to assist with storing and
+ * retrieving data for static leases
+ *
+ * Wade Berrier <wberrier@myrealbox.com> September 2004
+ *
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+
+
+/* Takes the address of the pointer to the static_leases linked list,
+ *   Address to a 6 byte mac address
+ *   Address to a 4 byte ip address */
+int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip)
+{
+       struct static_lease *cur;
+       struct static_lease *new_static_lease;
+
+       /* Build new node */
+       new_static_lease = xmalloc(sizeof(struct static_lease));
+       new_static_lease->mac = mac;
+       new_static_lease->ip = ip;
+       new_static_lease->next = NULL;
+
+       /* If it's the first node to be added... */
+       if (*lease_struct == NULL) {
+               *lease_struct = new_static_lease;
+       } else {
+               cur = *lease_struct;
+               while (cur->next) {
+                       cur = cur->next;
+               }
+
+               cur->next = new_static_lease;
+       }
+
+       return 1;
+}
+
+/* Check to see if a mac has an associated static lease */
+uint32_t getIpByMac(struct static_lease *lease_struct, void *arg)
+{
+       uint32_t return_ip;
+       struct static_lease *cur = lease_struct;
+       uint8_t *mac = arg;
+
+       return_ip = 0;
+
+       while (cur) {
+               /* If the client has the correct mac  */
+               if (memcmp(cur->mac, mac, 6) == 0) {
+                       return_ip = *(cur->ip);
+               }
+
+               cur = cur->next;
+       }
+
+       return return_ip;
+}
+
+/* Check to see if an ip is reserved as a static ip */
+uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip)
+{
+       struct static_lease *cur = lease_struct;
+
+       uint32_t return_val = 0;
+
+       while (cur) {
+               /* If the client has the correct ip  */
+               if (*cur->ip == ip)
+                       return_val = 1;
+
+               cur = cur->next;
+       }
+
+       return return_val;
+}
+
+#if ENABLE_FEATURE_UDHCP_DEBUG
+/* Print out static leases just to check what's going on */
+/* Takes the address of the pointer to the static_leases linked list */
+void printStaticLeases(struct static_lease **arg)
+{
+       /* Get a pointer to the linked list */
+       struct static_lease *cur = *arg;
+
+       while (cur) {
+               /* printf("PrintStaticLeases: Lease mac Address: %x\n", cur->mac); */
+               printf("PrintStaticLeases: Lease mac Value: %x\n", *(cur->mac));
+               /* printf("PrintStaticLeases: Lease ip Address: %x\n", cur->ip); */
+               printf("PrintStaticLeases: Lease ip Value: %x\n", *(cur->ip));
+
+               cur = cur->next;
+       }
+}
+#endif
diff --git a/networking/vconfig.c b/networking/vconfig.c
new file mode 100644 (file)
index 0000000..7b6c2fa
--- /dev/null
@@ -0,0 +1,164 @@
+/* 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) {
+               if (!*(table += table[0])) {
+                       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(struct vlan_ioctl_args));
+
+       ++argv;
+       p = xfind_str(cmds+2, *argv);
+       ifr.cmd = *p;
+       if (argc != p[-1]) {
+               bb_show_usage();
+       }
+
+       if (ifr.cmd == SET_VLAN_NAME_TYPE_CMD) { /* set_name_type */
+               ifr.u.name_type = *xfind_str(name_types+1, argv[1]);
+       } else {
+               if (strlen(argv[1]) >= IF_NAMESIZE) {
+                       bb_error_msg_and_die("if_name >= %d chars", IF_NAMESIZE);
+               }
+               strcpy(ifr.device1, argv[1]);
+               p = argv[2];
+
+               /* I suppose one could try to combine some of the function calls below,
+                * since ifr.u.flag, ifr.u.VID, and ifr.u.skb_priority are all same-sized
+                * (unsigned) int members of a unions.  But because of the range checking,
+                * doing so wouldn't save that much space and would also make maintainence
+                * more of a pain. */
+               if (ifr.cmd == SET_VLAN_FLAG_CMD) { /* set_flag */
+                       ifr.u.flag = xatoul_range(p, 0, 1);
+                       /* DM: in order to set reorder header, qos must be set */
+                       ifr.vlan_qos = xatoul_range(argv[3], 0, 7);
+               } else if (ifr.cmd == ADD_VLAN_CMD) { /* add */
+                       ifr.u.VID = xatoul_range(p, 0, VLAN_GROUP_ARRAY_LEN-1);
+               } else if (ifr.cmd != DEL_VLAN_CMD) { /* set_{egress|ingress}_map */
+                       ifr.u.skb_priority = xatou(p);
+                       ifr.vlan_qos = xatoul_range(argv[3], 0, 7);
+               }
+       }
+
+       fd = xsocket(AF_INET, SOCK_STREAM, 0);
+       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..f8adcd7
--- /dev/null
@@ -0,0 +1,805 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * wget - retrieve a file using HTTP or FTP
+ *
+ * Chip Rosenthal Covad Communications <chip@laserlink.net>
+ *
+ */
+
+#include <getopt.h>    /* for struct option */
+#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 int getttywidth(void)
+{
+       int width;
+       get_terminal_width_height(0, &width, NULL);
+       return width;
+}
+
+static void progressmeter(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 progressmeter */
+               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 = getttywidth() - 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 progressmeter */
+               alarm(0);
+               transferred = 0;
+               fputc('\n', stderr);
+       } else {
+               if (flag == -1) { /* first call to progressmeter */
+                       signal_SA_RESTART_empty_mask(SIGALRM, progressmeter);
+               }
+               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 progressmeter(int flag ATTRIBUTE_UNUSED) { }
+
+#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);
+               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);
+               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;
+}
+
+
+int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int wget_main(int argc ATTRIBUTE_UNUSED, 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 *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   = 0x1,
+               WGET_OPT_SPIDER     = 0x2,
+               WGET_OPT_QUIET      = 0x4,
+               WGET_OPT_OUTNAME    = 0x8,
+               WGET_OPT_PREFIX     = 0x10,
+               WGET_OPT_PROXY      = 0x20,
+               WGET_OPT_USER_AGENT = 0x40,
+               WGET_OPT_PASSIVE    = 0x80,
+               WGET_OPT_HEADER     = 0x100,
+       };
+#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"
+               "passive-ftp\0"      No_argument       "\xff"
+               "header\0"           Required_argument "\xfe"
+               ;
+#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:",
+                               &fname_out, &dir_prefix,
+                               &proxy_flag, &user_agent
+                               USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
+                               );
+       if (strcmp(proxy_flag, "off") == 0) {
+               /* Use the proxy if necessary */
+               use_proxy = 0;
+       }
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+       if (headers_llist) {
+               int size = 1;
+               char *cp;
+               llist_t *ll = headers_llist;
+               while (ll) {
+                       size += strlen(ll->data) + 2;
+                       ll = ll->link;
+               }
+               extra_headers = cp = xmalloc(size);
+               while (headers_llist) {
+                       cp += sprintf(cp, "%s\r\n", headers_llist->data);
+                       headers_llist = headers_llist->link;
+               }
+       }
+#endif
+
+       parse_url(argv[optind], &target);
+       server.host = target.host;
+       server.port = target.port;
+
+       /* Use the proxy if necessary */
+       if (use_proxy) {
+               proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
+               if (proxy && *proxy) {
+                       parse_url(proxy, &server);
+               } else {
+                       use_proxy = 0;
+               }
+       }
+
+       /* Guess an output filename, if 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 {
+                               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);
+#endif
+                       fprintf(sfp, "Connection: close\r\n\r\n");
+
+                       /*
+                       * Retrieve HTTP response line and check for "200" status code.
+                       */
+ read_response:
+                       if (fgets(buf, sizeof(buf), sfp) == NULL)
+                               bb_error_msg_and_die("no response from server");
+
+                       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:
+                               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 progressmeter (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))
+               progressmeter(-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))
+               progressmeter(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..8db840c
--- /dev/null
@@ -0,0 +1,549 @@
+/* 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;
+} ATTRIBUTE_PACKED;
+
+enum {
+/* 169.254.0.0 */
+       LINKLOCAL_ADDR = 0xa9fe0000,
+
+/* protocol timeout parameters, specified in seconds */
+       PROBE_WAIT = 1,
+       PROBE_MIN = 1,
+       PROBE_MAX = 2,
+       PROBE_NUM = 3,
+       MAX_CONFLICTS = 10,
+       RATE_LIMIT_INTERVAL = 60,
+       ANNOUNCE_WAIT = 2,
+       ANNOUNCE_NUM = 2,
+       ANNOUNCE_INTERVAL = 2,
+       DEFEND_INTERVAL = 10
+};
+
+/* States during the configuration process. */
+enum {
+       PROBE = 0,
+       RATE_LIMIT_PROBE,
+       ANNOUNCE,
+       MONITOR,
+       DEFEND
+};
+
+#define VDBG(...) do { } while (0)
+
+
+enum {
+       sock_fd = 3
+};
+
+struct globals {
+       char *intf;
+       struct sockaddr saddr;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define intf  (G.intf )
+#define saddr (G.saddr)
+
+
+/**
+ * Pick a random link local IP address on 169.254/16, except that
+ * the first and last 256 addresses are reserved.
+ */
+static void pick(struct in_addr *ip)
+{
+       unsigned tmp;
+
+       do {
+               tmp = rand() & IN_CLASSB_HOST;
+       } while (tmp > (IN_CLASSB_HOST - 0x0200));
+       ip->s_addr = htonl((LINKLOCAL_ADDR + 0x0100) + tmp);
+}
+
+/**
+ * Broadcast an ARP packet.
+ */
+static void arp(int op,
+       const struct ether_addr *source_eth, struct in_addr source_ip,
+       const struct ether_addr *target_eth, struct in_addr target_ip)
+{
+       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
+       xsendto(sock_fd, &p, sizeof(p), &saddr, sizeof(saddr));
+
+       // Currently all callers ignore errors, that's why returns are
+       // commented out...
+       //return 0;
+}
+
+/**
+ * Run a script. argv[2] is already NULL.
+ */
+static int run(char *argv[3], struct in_addr *ip)
+{
+       int status;
+       char *addr = addr; /* for gcc */
+       const char *fmt = "%s %s %s" + 3;
+
+       VDBG("%s run %s %s\n", intf, argv[0], argv[1]);
+
+       if (ip) {
+               addr = inet_ntoa(*ip);
+               setenv("ip", addr, 1);
+               fmt -= 3;
+       }
+       bb_info_msg(fmt, argv[1], intf, addr);
+
+       status = wait4pid(spawn(argv));
+       if (status < 0) {
+               bb_perror_msg("%s %s %s" + 3, argv[1], intf);
+               return -errno;
+       }
+       if (status != 0)
+               bb_error_msg("script %s %s failed, exitcode=%d", argv[0], argv[1], status);
+       return status;
+}
+
+/**
+ * Return milliseconds of random delay, up to "secs" seconds.
+ */
+static unsigned ALWAYS_INLINE 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 = PROBE;
+       struct ether_addr eth_addr;
+       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;
+               char *script_av[3];
+               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 script_av  (L.script_av )
+#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;
+
+       intf = argv[0];
+       script_av[0] = argv[1];
+       setenv("interface", intf, 1);
+
+       // initialize the interface (modprobe, ifup, etc)
+       script_av[1] = (char*)"init";
+       if (run(script_av, 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, 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(ifr.ifr_name, intf, sizeof(ifr.ifr_name));
+       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;
+               memcpy(&t, (char*)&eth_addr + 2, 4);
+               srand(t);
+       }
+       if (ip.s_addr == 0)
+               pick(&ip);
+
+       // FIXME cases to handle:
+       //  - zcip already running!
+       //  - link already has local address... just defend/update
+
+       // daemonize now; don't delay system startup
+       if (!FOREGROUND) {
+#if BB_MMU
+               bb_daemonize(0 /*was: DAEMON_CHDIR_ROOT*/);
+#endif
+               bb_info_msg("start, interface %s", intf);
+       }
+
+       // run the dynamic address negotiation protocol,
+       // restarting after address conflicts:
+       //  - start with some address we want to try
+       //  - short random delay
+       //  - arp probes to see if another host uses it
+       //  - arp announcements that we're claiming it
+       //  - use it
+       //  - defend it, within limits
+       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, 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, 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, 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, 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, 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
+                                       script_av[1] = (char*)"config";
+                                       run(script_av, &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;
+                               pick(&ip);
+                               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", intf);
+                                       if (ready) {
+                                               script_av[1] = (char*)"deconfig";
+                                               run(script_av, &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",
+                                       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", intf);
+                                               timeout_ms = RATE_LIMIT_INTERVAL * 1000;
+                                               state = RATE_LIMIT_PROBE;
+                                       }
+
+                                       // restart the whole protocol
+                                       pick(&ip);
+                                       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;
+                                       script_av[1] = (char*)"deconfig";
+                                       run(script_av, &ip);
+
+                                       // restart the whole protocol
+                                       pick(&ip);
+                                       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;
+                               pick(&ip);
+                               timeout_ms = 0;
+                               nprobes = 0;
+                               nclaims = 0;
+                               break;
+                       } // switch state
+                       break; // case 1 (packets arriving)
+               } // switch poll
+       } // while (1)
+}
diff --git a/printutils/Config.in b/printutils/Config.in
new file mode 100644 (file)
index 0000000..e0bf71b
--- /dev/null
@@ -0,0 +1,21 @@
+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..49e3fd7
--- /dev/null
@@ -0,0 +1,115 @@
+/* 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.
+ */
+#include "libbb.h"
+
+// TODO: xmalloc_reads is vulnerable to remote OOM attack!
+
+int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
+int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[])
+{
+       int spooling;
+       char *s, *queue;
+
+       // read command
+       s = xmalloc_reads(STDIN_FILENO, NULL);
+
+       // we understand only "receive job" command
+       if (2 != *s) {
+ unsupported_cmd:
+               printf("Command %02x %s\n",
+                       (unsigned char)s[0], "is not supported");
+               return EXIT_FAILURE;
+       }
+
+       // spool directory contains either links to real printer devices or just simple files
+       // these links or files are called "queues"
+       // OR
+       // if a directory named as given queue exists within spool directory
+       // then LPD enters spooling mode and just dumps both control and data files to it
+
+       // goto spool directory
+       if (argv[1])
+               xchdir(argv[1]);
+
+       // parse command: "\x2QUEUE_NAME\n"
+       queue = s + 1;
+       *strchrnul(s, '\n') = '\0';
+
+       // protect against "/../" attacks
+       if (queue[0] == '.' || strstr(queue, "/."))
+               return EXIT_FAILURE;
+
+       // queue is a directory -> chdir to it and enter spooling mode
+       spooling = chdir(queue) + 1; /* 0: cannot chdir, 1: done */
+
+       xdup2(STDOUT_FILENO, STDERR_FILENO);
+
+       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
+               write(STDOUT_FILENO, "", 1);
+
+               // get subcommand
+               s = xmalloc_reads(STDIN_FILENO, NULL);
+               if (!s)
+                       return EXIT_SUCCESS; // probably EOF
+               // we understand only "control file" or "data file" cmds
+               if (2 != s[0] && 3 != s[0])
+                       goto unsupported_cmd;
+
+               *strchrnul(s, '\n') = '\0';
+               // valid s must be of form: SUBCMD | LEN | SP | FNAME
+               // N.B. we bail out on any error
+               fname = strchr(s, ' ');
+               if (!fname) {
+                       printf("Command %02x %s\n",
+                               (unsigned char)s[0], "lacks filename");
+                       return EXIT_FAILURE;
+               }
+               *fname++ = '\0';
+               if (spooling) {
+                       // spooling mode: dump both files
+                       // make "/../" attacks in file names ineffective
+                       xchroot(".");
+                       // job in flight has mode 0200 "only writable"
+                       fd = xopen3(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200);
+               } else {
+                       // non-spooling mode:
+                       // 2: control file (ignoring), 3: data file
+                       fd = -1;
+                       if (3 == s[0])
+                               fd = xopen(queue, O_RDWR | O_APPEND);
+               }
+               expected_len = xatoi_u(s + 1);
+               real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len);
+               if (spooling && real_len != expected_len) {
+                       unlink(fname); // don't keep corrupted files
+                       printf("Expected %d but got %d bytes\n",
+                               expected_len, real_len);
+                       return EXIT_FAILURE;
+               }
+               // get ACK and see whether it is NUL (ok)
+               if (read(STDIN_FILENO, s, 1) != 1 || s[0] != 0) {
+                       // don't send error msg to peer - it obviously
+                       // don't follow the protocol, so probably
+                       // it can't understand us either
+                       return EXIT_FAILURE;
+               }
+               // chmod completely downloaded job as "readable+writable"
+               if (spooling)
+                       fchmod(fd, 0600);
+               close(fd); // NB: can do close(-1). Who cares?
+               free(s);
+       } /* while (1) */
+}
diff --git a/printutils/lpr.c b/printutils/lpr.c
new file mode 100644 (file)
index 0000000..5313d5a
--- /dev/null
@@ -0,0 +1,247 @@
+/* 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 ATTRIBUTE_UNUSED, 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 = bb_getpwuid(NULL, -1, 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 {
+               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;
+               while ((c = strchr(c, '\n')) != NULL) {
+                       c++;
+                       while (c[0] && c[1] == '\n')
+                               memmove(c, c+2, strlen(c+1)); /* strlen(c+1) == strlen(c+2) + 1 */
+               }
+
+               // send control file
+               if (opts & LPR_V)
+                       bb_error_msg("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." */
+               fdprintf(fd, "\x2" "%u c%s\n" "%s" "%c",
+                               (unsigned)strlen(controlfile),
+                               remote_filename, controlfile, '\0');
+               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);
+               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..585893e
--- /dev/null
@@ -0,0 +1,183 @@
+#
+# 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 (adds 2k bytes)"
+       default y
+       depends on TOP
+       help
+         Make top display CPU usage for each process.
+
+config FEATURE_TOP_CPU_GLOBAL_PERCENTS
+       bool "Show CPU global usage percentage (adds 0.5k bytes)"
+       default y
+       depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE
+       help
+         Makes top display "CPU: NN% usr NN% sys..." line.
+
+config FEATURE_TOP_DECIMALS
+       bool "Show 1/10th of a percent in CPU/mem statistics (adds 0.3k bytes)"
+       default n
+       depends on FEATURE_TOP_CPU_USAGE_PERCENTAGE
+       help
+         Show 1/10th of a percent in CPU/mem statistics.
+
+config FEATURE_TOPMEM
+       bool "topmem"
+       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
+       #huh?? select DATE
+       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..fd876d5
--- /dev/null
@@ -0,0 +1,343 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tiny fuser implementation
+ *
+ * Copyright 2004 Tony J. White
+ *
+ * May be distributed under the conditions of the
+ * GNU Library General Public License
+ */
+
+#include "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];
+       char addr[128];
+       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(path, "r");
+       if (!f)
+               return ilist;
+
+       while (fgets(line, MAX_LINE, f)) {
+               if (sscanf(line, "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x "
+                               "%*x:%*x %*x %*d %*d %llu",
+                               addr, &tmp_port, &uint64_inode) == 3
+               ) {
+                       if (strlen(addr) == 8 && (option_mask32 & OPT_IP6))
+                               continue;
+                       if (strlen(addr) > 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(fname, "r");
+       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;
+}
+
+static pid_list *scan_proc_pids(inode_list *ilist)
+{
+       DIR *d;
+       struct dirent *de;
+       pid_t pid;
+       pid_list *plist;
+
+       d = opendir(".");
+       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 ATTRIBUTE_UNUSED, 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);
+
+       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..a77d66e
--- /dev/null
@@ -0,0 +1,182 @@
+/* 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) MAIN_EXTERNALLY_VISIBLE;
+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;
+       }
+
+       /* -SIG */
+       signo = get_signum(&arg[1]);
+       if (signo < 0) { /* || signo > MAX_SIGNUM ? */
+               bb_error_msg("bad signal name '%s'", &arg[1]);
+               return EXIT_FAILURE;
+       }
+       arg = *++argv;
+       argc--;
+
+do_it_now:
+
+       if (killall5) {
+               pid_t sid;
+               procps_status_t* p = NULL;
+
+               /* Now stop all processes */
+               kill(-1, SIGSTOP);
+               /* Find out our own session id */
+               pid = getpid();
+               sid = getsid(pid);
+               /* Now kill all processes except our session */
+               while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_SID))) {
+                       if (p->sid != sid && p->pid != pid && p->pid != 1)
+                               kill(p->pid, signo);
+               }
+               /* And let them continue */
+               kill(-1, SIGCONT);
+               return 0;
+       }
+
+       /* Pid or name 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 */
+               pid = getpid();
+               while (arg) {
+                       pid_t* pidList;
+
+                       pidList = find_pid_by_name(arg);
+                       if (*pidList == 0) {
+                               errors++;
+                               if (!quiet)
+                                       bb_error_msg("%s: no process killed", arg);
+                       } else {
+                               pid_t *pl;
+
+                               for (pl = pidList; *pl; pl++) {
+                                       if (*pl == pid)
+                                               continue;
+                                       if (kill(*pl, signo) == 0)
+                                               continue;
+                                       errors++;
+                                       if (!quiet)
+                                               bb_perror_msg("cannot kill pid %u", (unsigned)*pl);
+                               }
+                       }
+                       free(pidList);
+                       arg = *++argv;
+               }
+               return errors;
+       }
+
+       /* Looks like they want to do a kill. Do that */
+       while (arg) {
+               /* 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..b6e754b
--- /dev/null
@@ -0,0 +1,897 @@
+/*
+** 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 <time.h>
+#include "libbb.h"
+
+typedef unsigned long long ullong;
+
+enum { PROC_FILE_SIZE = 4096 };
+
+typedef struct proc_file {
+       char *file;
+       //const char *name;
+       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(1, outbuf, sz);
+               cur_outbuf = outbuf;
+       }
+}
+
+static void put(const char *s)
+{
+       int sz = strlen(s);
+       if (sz > outbuf + sizeof(outbuf) - cur_outbuf)
+               sz = outbuf + sizeof(outbuf) - cur_outbuf;
+       memcpy(cur_outbuf, s, sz);
+       cur_outbuf += sz;
+}
+
+static void put_c(char c)
+{
+       if (cur_outbuf < outbuf + sizeof(outbuf))
+               *cur_outbuf++ = c;
+}
+
+static void put_question_marks(int count)
+{
+       while (count--)
+               put_c('?');
+}
+
+static void readfile_z(char *buf, int sz, 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.
+//     sz = open_read_close(fname, buf, sz-1);
+
+       int fd = xopen(fname, O_RDONLY);
+       buf[0] = '\0';
+       if (fd >= 0) {
+               sz = read(fd, buf, sz-1);
+               if (sz > 0) buf[sz] = '\0';
+               close(fd);
+       }
+}
+
+static const char* get_file(proc_file *pf)
+{
+       if (pf->last_gen != gen) {
+               pf->last_gen = gen;
+               // We allocate PROC_FILE_SIZE bytes. This wastes memory,
+               // but allows us to allocate only once (at first sample)
+               // per proc file, and reuse buffer for each sample
+               if (!pf->file)
+                       pf->file = xmalloc(PROC_FILE_SIZE);
+               readfile_z(pf->file, PROC_FILE_SIZE, proc_name[pf - &first_proc_file]);
+       }
+       return pf->file;
+}
+
+static inline ullong read_after_slash(const char *p)
+{
+       p = strchr(p, '/');
+       if (!p) return 0;
+       return strtoull(p+1, NULL, 10);
+}
+
+enum conv_type { conv_decimal, conv_slash };
+
+// Reads decimal values from line. Values start after key, for example:
+// "cpu  649369 0 341297 4336769..." - key is "cpu" here.
+// Values are stored in vec[]. arg_ptr has list of positions
+// we are interested in: for example: 1,2,5 - we want 1st, 2nd and 5th value.
+static int vrdval(const char* p, const char* key,
+       enum conv_type conv, ullong *vec, va_list arg_ptr)
+{
+       int indexline;
+       int indexnext;
+
+       p = strstr(p, key);
+       if (!p) return 1;
+
+       p += strlen(key);
+       indexline = 1;
+       indexnext = va_arg(arg_ptr, int);
+       while (1) {
+               while (*p == ' ' || *p == '\t') p++;
+               if (*p == '\n' || *p == '\0') break;
+
+               if (indexline == indexnext) { // read this value
+                       *vec++ = conv==conv_decimal ?
+                               strtoull(p, NULL, 10) :
+                               read_after_slash(p);
+                       indexnext = va_arg(arg_ptr, int);
+               }
+               while (*p > ' ') p++; // skip over value
+               indexline++;
+       }
+       return 0;
+}
+
+// Parses files with lines like "cpu0 21727 0 15718 1813856 9461 10485 0 0":
+// rdval(file_contents, "string_to_find", result_vector, value#, value#...)
+// value# start with 1
+static int rdval(const char* p, const char* key, ullong *vec, ...)
+{
+       va_list arg_ptr;
+       int result;
+
+       va_start(arg_ptr, vec);
+       result = vrdval(p, key, conv_decimal, vec, arg_ptr);
+       va_end(arg_ptr);
+
+       return result;
+}
+
+// Parses files with lines like "... ... ... 3/148 ...."
+static int rdval_loadavg(const char* p, ullong *vec, ...)
+{
+       va_list arg_ptr;
+       int result;
+
+       va_start(arg_ptr, vec);
+       result = vrdval(p, "", conv_slash, vec, arg_ptr);
+       va_end(arg_ptr);
+
+       return result;
+}
+
+// Parses /proc/diskstats
+//   1  2 3   4         5        6(rd)  7      8     9     10(wr) 11     12 13     14
+//   3  0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933
+//   3  1 hda1 0 0 0 0 <- ignore if only 4 fields
+static int rdval_diskstats(const char* p, ullong *vec)
+{
+       ullong rd = 0; // to avoid "warning: 'rd' might be used uninitialized"
+       int indexline = 0;
+       vec[0] = 0;
+       vec[1] = 0;
+       while (1) {
+               indexline++;
+               while (*p == ' ' || *p == '\t') p++;
+               if (*p == '\0') break;
+               if (*p == '\n') {
+                       indexline = 0;
+                       p++;
+                       continue;
+               }
+               if (indexline == 6) {
+                       rd = strtoull(p, NULL, 10);
+               } else if (indexline == 10) {
+                       vec[0] += rd;  // TODO: *sectorsize (don't know how to find out sectorsize)
+                       vec[1] += strtoull(p, NULL, 10);
+                       while (*p != '\n' && *p != '\0') p++;
+                       continue;
+               }
+               while (*p > ' ') p++; // skip over value
+       }
+       return 0;
+}
+
+static void scale(ullong ul)
+{
+       char buf[5];
+
+       /* 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 ATTRIBUTE_UNUSED)
+{
+}
+
+static s_stat* init_literal(void)
+{
+       s_stat *s = xmalloc(sizeof(s_stat));
+       s->collect = collect_literal;
+       return (s_stat*)s;
+}
+
+static s_stat* init_delay(const char *param)
+{
+       delta = bb_strtoi(param, NULL, 0) * 1000;
+       deltanz = delta > 0 ? delta : 1;
+       need_seconds = (1000000%deltanz) != 0;
+       return NULL;
+}
+
+static s_stat* init_cr(const char *param ATTRIBUTE_UNUSED)
+{
+       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 = xmalloc(sizeof(cpu_stat));
+       s->collect = collect_cpu;
+       sz = strtol(param, NULL, 0);
+       if (sz < 10) sz = 10;
+       if (sz > 1000) sz = 1000;
+       s->bar = xmalloc(sz+1);
+       s->bar[sz] = '\0';
+       s->bar_sz = sz;
+       return (s_stat*)s;
+}
+
+
+S_STAT(int_stat)
+       ullong old;
+       int no;
+S_STAT_END(int_stat)
+
+static void collect_int(int_stat *s)
+{
+       ullong data[1];
+       ullong old;
+
+       if (rdval(get_file(&proc_stat), "intr", data, s->no)) {
+               put_question_marks(4);
+               return;
+       }
+
+       old = s->old;
+       if (data[0] < old) old = data[0];               //sanitize
+       s->old = data[0];
+       scale(data[0] - old);
+}
+
+static s_stat* init_int(const char *param)
+{
+       int_stat *s = xmalloc(sizeof(int_stat));
+       s->collect = collect_int;
+       if (param[0]=='\0') {
+               s->no = 1;
+       } else {
+               int n = strtoul(param, NULL, 0);
+               s->no = n+2;
+       }
+       return (s_stat*)s;
+}
+
+
+S_STAT(ctx_stat)
+       ullong old;
+S_STAT_END(ctx_stat)
+
+static void collect_ctx(ctx_stat *s)
+{
+       ullong data[1];
+       ullong old;
+
+       if (rdval(get_file(&proc_stat), "ctxt", data, 1)) {
+               put_question_marks(4);
+               return;
+       }
+
+       old = s->old;
+       if (data[0] < old) old = data[0];               //sanitize
+       s->old = data[0];
+       scale(data[0] - old);
+}
+
+static s_stat* init_ctx(const char *param ATTRIBUTE_UNUSED)
+{
+       ctx_stat *s = xmalloc(sizeof(ctx_stat));
+       s->collect = collect_ctx;
+       return (s_stat*)s;
+}
+
+
+S_STAT(blk_stat)
+       const char* lookfor;
+       ullong old[2];
+S_STAT_END(blk_stat)
+
+static void collect_blk(blk_stat *s)
+{
+       ullong data[2];
+       int i;
+
+       if (is26) {
+               i = rdval_diskstats(get_file(&proc_diskstats), data);
+       } else {
+               i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2);
+               // Linux 2.4 reports bio in Kbytes, convert to sectors:
+               data[0] *= 2;
+               data[1] *= 2;
+       }
+       if (i) {
+               put_question_marks(9);
+               return;
+       }
+
+       for (i=0; i<2; i++) {
+               ullong old = s->old[i];
+               if (data[i] < old) old = data[i];               //sanitize
+               s->old[i] = data[i];
+               data[i] -= old;
+       }
+       scale(data[0]*512); // TODO: *sectorsize
+       put_c(' ');
+       scale(data[1]*512);
+}
+
+static s_stat* init_blk(const char *param ATTRIBUTE_UNUSED)
+{
+       blk_stat *s = xmalloc(sizeof(blk_stat));
+       s->collect = collect_blk;
+       s->lookfor = "page";
+       return (s_stat*)s;
+}
+
+
+S_STAT(fork_stat)
+       ullong old;
+S_STAT_END(fork_stat)
+
+static void collect_thread_nr(fork_stat *s ATTRIBUTE_UNUSED)
+{
+       ullong data[1];
+
+       if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) {
+               put_question_marks(4);
+               return;
+       }
+       scale(data[0]);
+}
+
+static void collect_fork(fork_stat *s)
+{
+       ullong data[1];
+       ullong old;
+
+       if (rdval(get_file(&proc_stat), "processes", data, 1)) {
+               put_question_marks(4);
+               return;
+       }
+
+       old = s->old;
+       if (data[0] < old) old = data[0];       //sanitize
+       s->old = data[0];
+       scale(data[0] - old);
+}
+
+static s_stat* init_fork(const char *param)
+{
+       fork_stat *s = xmalloc(sizeof(fork_stat));
+       if (*param == 'n') {
+               s->collect = collect_thread_nr;
+       } else {
+               s->collect = collect_fork;
+       }
+       return (s_stat*)s;
+}
+
+
+S_STAT(if_stat)
+       ullong old[4];
+       const char *device;
+       char *device_colon;
+S_STAT_END(if_stat)
+
+static void collect_if(if_stat *s)
+{
+       ullong data[4];
+       int i;
+
+       if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) {
+               put_question_marks(10);
+               return;
+       }
+
+       for (i=0; i<4; i++) {
+               ullong old = s->old[i];
+               if (data[i] < old) old = data[i];               //sanitize
+               s->old[i] = data[i];
+               data[i] -= old;
+       }
+       put_c(data[1] ? '*' : ' ');
+       scale(data[0]);
+       put_c(data[3] ? '*' : ' ');
+       scale(data[2]);
+}
+
+static s_stat* init_if(const char *device)
+{
+       if_stat *s = xmalloc(sizeof(if_stat));
+
+       if (!device || !device[0])
+               bb_show_usage();
+       s->collect = collect_if;
+
+       s->device = device;
+       s->device_colon = xmalloc(strlen(device)+2);
+       strcpy(s->device_colon, device);
+       strcat(s->device_colon, ":");
+       return (s_stat*)s;
+}
+
+
+S_STAT(mem_stat)
+       char opt;
+S_STAT_END(mem_stat)
+
+// "Memory" value should not include any caches.
+// IOW: neither "ls -laR /" nor heavy read/write activity
+//      should affect it. We'd like to also include any
+//      long-term allocated kernel-side mem, but it is hard
+//      to figure out. For now, bufs, cached & slab are
+//      counted as "free" memory
+//2.6.16:
+//MemTotal:       773280 kB
+//MemFree:         25912 kB - genuinely free
+//Buffers:        320672 kB - cache
+//Cached:         146396 kB - cache
+//SwapCached:          0 kB
+//Active:         183064 kB
+//Inactive:       356892 kB
+//HighTotal:           0 kB
+//HighFree:            0 kB
+//LowTotal:       773280 kB
+//LowFree:         25912 kB
+//SwapTotal:      131064 kB
+//SwapFree:       131064 kB
+//Dirty:              48 kB
+//Writeback:           0 kB
+//Mapped:          96620 kB
+//Slab:           200668 kB - takes 7 Mb on my box fresh after boot,
+//                            but includes dentries and inodes
+//                            (== can take arbitrary amount of mem)
+//CommitLimit:    517704 kB
+//Committed_AS:   236776 kB
+//PageTables:       1248 kB
+//VmallocTotal:   516052 kB
+//VmallocUsed:      3852 kB
+//VmallocChunk:   512096 kB
+//HugePages_Total:     0
+//HugePages_Free:      0
+//Hugepagesize:     4096 kB
+static void collect_mem(mem_stat *s)
+{
+       ullong m_total = 0;
+       ullong m_free = 0;
+       ullong m_bufs = 0;
+       ullong m_cached = 0;
+       ullong m_slab = 0;
+
+       if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) {
+               put_question_marks(4);
+               return;
+       }
+       if (s->opt == '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 = xmalloc(sizeof(mem_stat));
+       s->collect = collect_mem;
+       s->opt = param[0];
+       return (s_stat*)s;
+}
+
+
+S_STAT(swp_stat)
+S_STAT_END(swp_stat)
+
+static void collect_swp(swp_stat *s ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED)
+{
+       swp_stat *s = xmalloc(sizeof(swp_stat));
+       s->collect = collect_swp;
+       return (s_stat*)s;
+}
+
+
+S_STAT(fd_stat)
+S_STAT_END(fd_stat)
+
+static void collect_fd(fd_stat *s ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED)
+{
+       fd_stat *s = xmalloc(sizeof(fd_stat));
+       s->collect = collect_fd;
+       return (s_stat*)s;
+}
+
+
+S_STAT(time_stat)
+       int prec;
+       int scale;
+S_STAT_END(time_stat)
+
+static void collect_time(time_stat *s)
+{
+       char buf[sizeof("12:34:56.123456")];
+       struct tm* tm;
+       int us = tv.tv_usec + s->scale/2;
+       time_t t = tv.tv_sec;
+
+       if (us >= 1000000) {
+               t++;
+               us -= 1000000;
+       }
+       tm = localtime(&t);
+
+       sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+       if (s->prec)
+               sprintf(buf+8, ".%0*d", s->prec, us / s->scale);
+       put(buf);
+}
+
+static s_stat* init_time(const char *param)
+{
+       int prec;
+       time_stat *s = xmalloc(sizeof(time_stat));
+
+       s->collect = collect_time;
+       prec = param[0]-'0';
+       if (prec < 0) prec = 0;
+       else if (prec > 6) prec = 6;
+       s->prec = prec;
+       s->scale = 1;
+       while (prec++ < 6)
+               s->scale *= 10;
+       return (s_stat*)s;
+}
+
+static void collect_info(s_stat *s)
+{
+       gen ^= 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)) > 0)
+               is26 = (strstr(buf, " 2.4.")==NULL);
+
+       // Can use argv[1] directly, but this will mess up
+       // parameters as seen by e.g. ps. Making a copy...
+       cur = xstrdup(argv[1]);
+       while (1) {
+               char *param, *p;
+               prev = cur;
+ again:
+               cur = strchr(cur, '%');
+               if (!cur)
+                       break;
+               if (cur[1] == '%') {    // %%
+                       strcpy(cur, cur+1);
+                       cur++;
+                       goto again;
+               }
+               *cur++ = '\0';          // overwrite %
+               if (cur[0] == '[') {
+                       // format: %[foptstring]
+                       cur++;
+                       p = strchr(options, cur[0]);
+                       param = cur+1;
+                       while (cur[0] != ']') {
+                               if (!cur[0])
+                                       bb_show_usage();
+                               cur++;
+                       }
+                       *cur++ = '\0';  // overwrite [
+               } else {
+                       // format: %NNNNNNf
+                       param = cur;
+                       while (cur[0] >= '0' && cur[0] <= '9')
+                               cur++;
+                       if (!cur[0])
+                               bb_show_usage();
+                       p = strchr(options, cur[0]);
+                       *cur++ = '\0';  // overwrite format char
+               }
+               if (!p)
+                       bb_show_usage();
+               s = init_functions[p-options](param);
+               if (s) {
+                       s->label = prev;
+                       s->next = 0;
+                       if (!first)
+                               first = s;
+                       else
+                               last->next = s;
+                       last = s;
+               } else {
+                       // %NNNNd or %r option. remove it from string
+                       strcpy(prev + strlen(prev), cur);
+                       cur = prev;
+               }
+       }
+       if (prev[0]) {
+               s = init_literal();
+               s->label = prev;
+               s->next = 0;
+               if (!first)
+                       first = s;
+               else
+                       last->next = s;
+               last = s;
+       }
+
+       // Generate first samples but do not print them, they're bogus
+       collect_info(first);
+       reset_outbuf();
+       if (delta >= 0) {
+               gettimeofday(&tv, 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..1ffc87d
--- /dev/null
@@ -0,0 +1,139 @@
+/* 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 <getopt.h>
+
+#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 ATTRIBUTE_UNUSED, 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;
+               /* 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 == 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..46e646d
--- /dev/null
@@ -0,0 +1,88 @@
+/* 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       unsigned first = 1;
+       unsigned opt;
+#if ENABLE_FEATURE_PIDOF_OMIT
+       char ppid_str[sizeof(int)*3 + 1];
+       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 (omits_p) {
+                       /* are we asked to exclude the parent's process ID?  */
+                       if (strcmp(omits_p->data, "%PPID") == 0) {
+                               sprintf(ppid_str, "%u", (unsigned)getppid());
+                               omits_p->data = ppid_str;
+                       }
+                       omits_p = omits_p->link;
+               }
+       }
+#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) == *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..aeb8cec
--- /dev/null
@@ -0,0 +1,572 @@
+/* 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 SELINIX_O_PREFIX "label,"
+#define DEFAULT_O_STR    (SELINIX_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)
+{
+       int i = out_cnt++;
+       out = xrealloc(out, out_cnt * sizeof(*out));
+       return &out[i];
+}
+
+static const ps_out_t* find_out_spec(const char *name)
+{
+       int i;
+       for (i = 0; i < 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 ATTRIBUTE_UNUSED, 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(opt_o->data);
+                       opt_o = opt_o->link;
+               } 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(SELINIX_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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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
+       int terminal_width;
+       int w_count = 0;
+#endif
+
+#if ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX
+#if ENABLE_FEATURE_PS_WIDE
+       opt_complementary = "-:ww";
+       USE_SELINUX(i =) getopt32(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..d2dcf15
--- /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 ATTRIBUTE_UNUSED, 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..1995382
--- /dev/null
@@ -0,0 +1,294 @@
+/* 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"
+
+static int sysctl_read_setting(const char *setting);
+static int sysctl_write_setting(const char *setting);
+static int sysctl_display_all(const char *path);
+static int sysctl_preload_file_and_exit(const char *filename);
+
+static const char ETC_SYSCTL_CONF[] ALIGN1 = "/etc/sysctl.conf";
+static const char PROC_SYS[] ALIGN1 = "/proc/sys/";
+enum { strlen_PROC_SYS = sizeof(PROC_SYS) - 1 };
+
+/* error messages */
+static const char ERR_MALFORMED_SETTING[] ALIGN1 =
+       "error: malformed setting '%s'";
+static const char ERR_NO_EQUALS[] ALIGN1 =
+       "error: '%s' must be of the form name=value";
+static const char ERR_INVALID_KEY[] ALIGN1 =
+       "error: '%s' is an unknown key";
+static const char ERR_UNKNOWN_WRITING[] ALIGN1 =
+       "error setting key '%s'";
+static const char ERR_UNKNOWN_READING[] ALIGN1 =
+       "error reading key '%s'";
+static const char ERR_PERMISSION_DENIED[] ALIGN1 =
+       "error: permission denied on key '%s'";
+static const char WARN_BAD_LINE[] ALIGN1 =
+       "warning: %s(%d): invalid syntax, continuing";
+
+
+static void dwrite_str(int fd, const char *buf)
+{
+       write(fd, buf, strlen(buf));
+}
+
+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,
+};
+
+int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sysctl_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int retval;
+       int opt;
+
+       opt = getopt32(argv, "+neAapw"); /* '+' - stop on first non-option */
+       argv += optind;
+       opt ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS);
+       option_mask32 ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS);
+
+       if (opt & (FLAG_TABLE_FORMAT | FLAG_SHOW_ALL))
+               return sysctl_display_all(PROC_SYS);
+       if (opt & FLAG_PRELOAD_FILE)
+               return sysctl_preload_file_and_exit(*argv ? *argv : ETC_SYSCTL_CONF);
+
+       retval = 0;
+       while (*argv) {
+               if (opt & FLAG_WRITE)
+                       retval |= sysctl_write_setting(*argv);
+               else
+                       retval |= sysctl_read_setting(*argv);
+               argv++;
+       }
+
+       return retval;
+} /* end sysctl_main() */
+
+/*
+ * preload the sysctl's from a conf file
+ * - we parse the file and then reform it (strip out whitespace)
+ */
+#define PRELOAD_BUF 256
+
+static int sysctl_preload_file_and_exit(const char *filename)
+{
+       int lineno;
+       char oneline[PRELOAD_BUF];
+       char buffer[PRELOAD_BUF];
+       char *name, *value;
+       FILE *fp;
+
+       fp = xfopen(filename, "r");
+
+       lineno = 0;
+       while (fgets(oneline, sizeof(oneline) - 1, fp)) {
+               lineno++;
+               trim(oneline);
+               if (oneline[0] == '#' || oneline[0] == ';')
+                       continue;
+               if (!oneline[0] || !oneline[1])
+                       continue;
+
+               name = strtok(oneline, "=");
+               if (!name) {
+                       bb_error_msg(WARN_BAD_LINE, filename, lineno);
+                       continue;
+               }
+               trim(name);
+               if (!*name) {
+                       bb_error_msg(WARN_BAD_LINE, filename, lineno);
+                       continue;
+               }
+
+               value = strtok(NULL, "\n\r");
+               if (!value) {
+                       bb_error_msg(WARN_BAD_LINE, filename, lineno);
+                       continue;
+               }
+               while (*value == ' ' || *value == '\t')
+                       value++;
+               if (!*value) {
+                       bb_error_msg(WARN_BAD_LINE, filename, lineno);
+                       continue;
+               }
+
+               /* safe because sizeof(oneline) == sizeof(buffer) */
+               sprintf(buffer, "%s=%s", name, value);
+               sysctl_write_setting(buffer);
+       }
+       if (ENABLE_FEATURE_CLEAN_UP)
+               fclose(fp);
+       return 0;
+} /* end sysctl_preload_file_and_exit() */
+
+/*
+ *     Write a single sysctl setting
+ */
+static int sysctl_write_setting(const char *setting)
+{
+       int retval;
+       const char *name;
+       const char *value;
+       const char *equals;
+       char *tmpname, *outname, *cptr;
+       int fd;
+
+       name = setting;
+       equals = strchr(setting, '=');
+       if (!equals) {
+               bb_error_msg(ERR_NO_EQUALS, setting);
+               return EXIT_FAILURE;
+       }
+
+       value = equals + 1;     /* point to the value in name=value */
+       if (name == equals || !*value) {
+               bb_error_msg(ERR_MALFORMED_SETTING, setting);
+               return EXIT_FAILURE;
+       }
+
+       tmpname = xasprintf("%s%.*s", PROC_SYS, (int)(equals - name), name);
+       outname = xstrdup(tmpname + strlen_PROC_SYS);
+
+       while ((cptr = strchr(tmpname, '.')) != NULL)
+               *cptr = '/';
+
+       while ((cptr = strchr(outname, '/')) != NULL)
+               *cptr = '.';
+
+       fd = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+       if (fd < 0) {
+               switch (errno) {
+               case ENOENT:
+                       if (option_mask32 & FLAG_SHOW_KEY_ERRORS)
+                               bb_error_msg(ERR_INVALID_KEY, outname);
+                       break;
+               case EACCES:
+                       bb_perror_msg(ERR_PERMISSION_DENIED, outname);
+                       break;
+               default:
+                       bb_perror_msg(ERR_UNKNOWN_WRITING, outname);
+                       break;
+               }
+               retval = EXIT_FAILURE;
+       } else {
+               dwrite_str(fd, value);
+               close(fd);
+               if (option_mask32 & FLAG_SHOW_KEYS) {
+                       printf("%s = ", outname);
+               }
+               puts(value);
+               retval = EXIT_SUCCESS;
+       }
+
+       free(tmpname);
+       free(outname);
+       return retval;
+} /* end sysctl_write_setting() */
+
+/*
+ *     Read a sysctl setting
+ */
+static int sysctl_read_setting(const char *name)
+{
+       int retval;
+       char *tmpname, *outname, *cptr;
+       char inbuf[1025];
+       FILE *fp;
+
+       if (!*name) {
+               if (option_mask32 & FLAG_SHOW_KEY_ERRORS)
+                       bb_error_msg(ERR_INVALID_KEY, name);
+               return -1;
+       }
+
+       tmpname = concat_path_file(PROC_SYS, name);
+       outname = xstrdup(tmpname + strlen_PROC_SYS);
+
+       while ((cptr = strchr(tmpname, '.')) != NULL)
+               *cptr = '/';
+       while ((cptr = strchr(outname, '/')) != NULL)
+               *cptr = '.';
+
+       fp = fopen(tmpname, "r");
+       if (fp == NULL) {
+               switch (errno) {
+               case ENOENT:
+                       if (option_mask32 & FLAG_SHOW_KEY_ERRORS)
+                               bb_error_msg(ERR_INVALID_KEY, outname);
+                       break;
+               case EACCES:
+                       bb_error_msg(ERR_PERMISSION_DENIED, outname);
+                       break;
+               default:
+                       bb_perror_msg(ERR_UNKNOWN_READING, outname);
+                       break;
+               }
+               retval = EXIT_FAILURE;
+       } else {
+               while (fgets(inbuf, sizeof(inbuf) - 1, fp)) {
+                       if (option_mask32 & FLAG_SHOW_KEYS) {
+                               printf("%s = ", outname);
+                       }
+                       fputs(inbuf, stdout);
+               }
+               fclose(fp);
+               retval = EXIT_SUCCESS;
+       }
+
+       free(tmpname);
+       free(outname);
+       return retval;
+} /* end sysctl_read_setting() */
+
+/*
+ *     Display all the sysctl settings
+ */
+static int sysctl_display_all(const char *path)
+{
+       int retval = 0;
+       DIR *dp;
+       struct dirent *de;
+       char *tmpdir;
+       struct stat ts;
+
+       dp = opendir(path);
+       if (!dp) {
+               return EXIT_FAILURE;
+       }
+       while ((de = readdir(dp)) != NULL) {
+               tmpdir = concat_subpath_file(path, de->d_name);
+               if (tmpdir == NULL)
+                       continue; /* . or .. */
+               if (stat(tmpdir, &ts) != 0) {
+                       bb_perror_msg(tmpdir);
+               } else if (S_ISDIR(ts.st_mode)) {
+                       retval |= sysctl_display_all(tmpdir);
+               } else {
+                       retval |= sysctl_read_setting(tmpdir + strlen_PROC_SYS);
+               }
+               free(tmpdir);
+       } /* end while */
+       closedir(dp);
+
+       return retval;
+} /* end sysctl_display_all() */
diff --git a/procps/top.c b/procps/top.c
new file mode 100644 (file)
index 0000000..206f9e8
--- /dev/null
@@ -0,0 +1,945 @@
+/* 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>
+ */
+
+/* Original code Copyrights */
+/*
+ * Copyright (c) 1992 Branko Lankester
+ * Copyright (c) 1992 Roger Binns
+ * Copyright (C) 1994-1996 Charles L. Blake.
+ * Copyright (C) 1992-1998 Michael K. Johnson
+ * May be distributed under the conditions of the
+ * GNU Library General Public License
+ */
+
+#include "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];
+} top_status_t;
+
+typedef struct jiffy_counts_t {
+       unsigned long long usr,nic,sys,idle,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;
+       unsigned 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_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 jif, prev_jif;
+       /* int hist_iterations; */
+       unsigned total_pcpu;
+       /* unsigned long total_vsz; */
+#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 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 jif              (G.jif               )
+#define prev_jif         (G.prev_jif          )
+#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 void get_jiffy_counts(void)
+{
+       FILE* fp = xfopen("stat", "r");
+       prev_jif = jif;
+       if (fscanf(fp, "cpu  %lld %lld %lld %lld %lld %lld %lld %lld",
+                       &jif.usr,&jif.nic,&jif.sys,&jif.idle,
+                       &jif.iowait,&jif.irq,&jif.softirq,&jif.steal) < 4) {
+               bb_error_msg_and_die("can't read /proc/stat");
+       }
+       fclose(fp);
+       jif.total = jif.usr + jif.nic + jif.sys + jif.idle
+                       + jif.iowait + jif.irq + jif.softirq + jif.steal;
+       /* procps 2.x does not count iowait as busy time */
+       jif.busy = jif.total - jif.idle - jif.iowait;
+}
+
+
+static void do_stats(void)
+{
+       top_status_t *cur;
+       pid_t pid;
+       int i, last_i, n;
+       struct save_hist *new_hist;
+
+       get_jiffy_counts();
+       total_pcpu = 0;
+       /* total_vsz = 0; */
+       new_hist = xmalloc(sizeof(struct save_hist)*ntop);
+       /*
+        * Make a pass through the data to get stats.
+        */
+       /* hist_iterations = 0; */
+       i = 0;
+       for (n = 0; n < ntop; n++) {
+               cur = top + n;
+
+               /*
+                * Calculate time in cur process.  Time is sum of user time
+                * and system time
+                */
+               pid = cur->pid;
+               new_hist[n].ticks = cur->ticks;
+               new_hist[n].pid = pid;
+
+               /* find matching entry from previous pass */
+               cur->pcpu = 0;
+               /* do not start at index 0, continue at last used one
+                * (brought hist_iterations from ~14000 down to 172) */
+               last_i = i;
+               if (prev_hist_count) do {
+                       if (prev_hist[i].pid == pid) {
+                               cur->pcpu = cur->ticks - prev_hist[i].ticks;
+                               total_pcpu += cur->pcpu;
+                               break;
+                       }
+                       i = (i+1) % prev_hist_count;
+                       /* hist_iterations++; */
+               } while (i != last_i);
+               /* total_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
+
+static unsigned long display_header(int scr_width)
+{
+       FILE *fp;
+       char buf[80];
+       char scrbuf[80];
+       unsigned long total, used, mfree, shared, buffers, cached;
+#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS
+       unsigned total_diff;
+#endif
+
+       /* read memory info */
+       fp = xfopen("meminfo", "r");
+
+       /*
+        * Old kernels (such as 2.4.x) had a nice summary of memory info that
+        * we could parse, however this is gone entirely in 2.6. Try parsing
+        * the old way first, and if that fails, parse each field manually.
+        *
+        * First, we read in the first line. Old kernels will have bogus
+        * strings we don't care about, whereas new kernels will start right
+        * out with MemTotal:
+        *                              -- PFM.
+        */
+       if (fscanf(fp, "MemTotal: %lu %s\n", &total, buf) != 2) {
+               fgets(buf, sizeof(buf), fp);    /* skip first line */
+
+               fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
+                       &total, &used, &mfree, &shared, &buffers, &cached);
+               /* 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 > 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);
+
+#if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS
+       /*
+        * xxx% = (jif.xxx - prev_jif.xxx) / (jif.total - prev_jif.total) * 100%
+        */
+       /* using (unsigned) casts to make operations cheaper */
+       total_diff = ((unsigned)(jif.total - prev_jif.total) ? : 1);
+#if ENABLE_FEATURE_TOP_DECIMALS
+/* Generated code is approx +0.3k */
+#define CALC_STAT(xxx) char xxx[8]
+#define SHOW_STAT(xxx) fmt_100percent_8(xxx, (unsigned)(jif.xxx - prev_jif.xxx), total_diff)
+#define FMT "%s"
+#else
+#define CALC_STAT(xxx) unsigned xxx = 100 * (unsigned)(jif.xxx - prev_jif.xxx) / total_diff
+#define SHOW_STAT(xxx) xxx
+#define FMT "%4u%% "
+#endif
+       { /* need 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. */
+                       "CPU:"FMT"usr"FMT"sys"FMT"nice"FMT"idle"FMT"io"FMT"irq"FMT"softirq",
+                       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
+#endif
+
+       /* read load average as a string */
+       buf[0] = '\0';
+       open_read_close("loadavg", buf, sizeof("N.NN N.NN N.NN")-1);
+       buf[sizeof("N.NN N.NN N.NN")-1] = '\0';
+       snprintf(scrbuf, scr_width, "Load average: %s", buf);
+       puts(scrbuf);
+
+       return total;
+}
+
+static NOINLINE void display_process_list(int count, int scr_width)
+{
+       enum {
+               BITS_PER_INT = sizeof(int)*8
+       };
+
+       top_status_t *s = top;
+       char vsz_str_buf[8];
+       unsigned long total_memory = display_header(scr_width); /* 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;
+
+       /* 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 %CPU COMMAND");
+#else
+
+       /* !CPU_USAGE_PERCENTAGE */
+       printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width,
+               "  PID  PPID USER     STAT   VSZ %MEM COMMAND");
+#endif
+
+#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 = jif.busy - prev_jif.busy;
+       /* This happens if there were lots of short-lived processes
+        * between two top updates (e.g. compilation) */
+       if (total_pcpu < busy_jifs) total_pcpu = busy_jifs;
+
+       /*
+        * CPU% = s->pcpu/sum(s->pcpu) * busy_cpu_ticks/total_cpu_ticks
+        * (pcpu is delta of sys+user time between samples)
+        */
+       /* (jif.xxx - prev_jif.xxx) and s->pcpu are
+        * in 0..~64000 range (HZ*update_interval).
+        * we assume that unsigned is at least 32-bit.
+        */
+       pcpu_shift = 6;
+       pcpu_scale = (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)(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
+
+       scr_width += 2; /* account for leading '\n' and trailing NUL */
+       /* Ok, all preliminary data is ready, go through the list */
+       while (count-- > 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_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_CPU_USAGE_PERCENTAGE
+                               , SHOW_STAT(pcpu)
+#endif
+               );
+               if (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,
+                       jif.busy - prev_jif.busy, 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
+#include <termios.h>
+#include <signal.h>
+
+static void reset_term(void)
+{
+       tcsetattr(0, 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 ATTRIBUTE_UNUSED)
+{
+       reset_term();
+       exit(1);
+}
+#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)
+{
+       char linebuf[128];
+       int 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("meminfo", "r");
+       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);
+#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 count, 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);
+       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);
+
+       while (--count >= 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 > 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 count, 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
+               | 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 ATTRIBUTE_UNUSED, char **argv)
+{
+       int count, lines, col;
+       unsigned interval;
+       int iterations;
+       char *sinterval;
+       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 */
+
+       /* all args are options; -n NUM */
+       opt_complementary = "-:n+";
+       getopt32(argv, "d:n:b", &sinterval, &iterations);
+       if (option_mask32 & OPT_d) {
+               /* Need to limit it to not overflow poll timeout */
+               interval = xatou16(sinterval); // -d
+       }
+
+       /* 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(0, TCSANOW, (void *) &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;
+               if (!ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS && scan_mask == TOP_MASK)
+                       lines -= 3;
+               else
+                       lines -= 4;
+
+               /* 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(top, (++ntop) * sizeof(*top));
+                               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);
+                       } else { /* TOPMEM */
+#if ENABLE_FEATURE_TOPMEM
+                               if (!(p->mapped_ro | p->mapped_rw))
+                                       continue; /* kernel threads are ignored */
+                               n = ntop;
+                               top = xrealloc(topmem, (++ntop) * sizeof(*topmem));
+                               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
+               count = lines;
+               if (OPT_BATCH_MODE || count > ntop) {
+                       count = ntop;
+               }
+               if (scan_mask == TOP_MASK)
+                       display_process_list(count, col);
+#if ENABLE_FEATURE_TOPMEM
+               else
+                       display_topmem_process_list(count, 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(0, &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
+#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..b729055
--- /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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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..5b774e8
--- /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 ATTRIBUTE_UNUSED, char **argv)
+{
+       unsigned opt;
+       unsigned period = 2;
+       int 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 = -1; // make sure first time new_width != width
+       header = NULL;
+       while (1) {
+               printf("\033[H\033[J");
+               if (!(opt & 0x2)) { // no -t
+                       const int 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, 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..8a7deea
--- /dev/null
@@ -0,0 +1,66 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Runit Utilities"
+
+config RUNSV
+       bool "runsv"
+       default n
+       help
+         runsv starts and monitors a service and optionally an appendant log
+         service.
+
+config RUNSVDIR
+       bool "runsvdir"
+       default n
+       help
+         runsvdir starts a runsv process for each subdirectory, or symlink to
+         a directory, in the services directory dir, up to a limit of 1000
+         subdirectories, and restarts a runsv process if it terminates.
+
+config SV
+       bool "sv"
+       default n
+       help
+         sv reports the current status and controls the state of services
+         monitored by the runsv supervisor.
+
+config SVLOGD
+       bool "svlogd"
+       default n
+       help
+         svlogd continuously reads log data from its standard input, optionally
+         filters log messages, and writes the data to one or more automatically
+         rotated logs.
+
+config CHPST
+       bool "chpst"
+       default n
+       help
+         chpst changes the process state according to the given options, and
+         execs specified program.
+
+config SETUIDGID
+       bool "setuidgid"
+       help
+         Sets soft resource limits as specified by options
+
+config ENVUIDGID
+       bool "envuidgid"
+       help
+         Sets $UID to account's uid and $GID to account's gid
+
+config ENVDIR
+       bool "envdir"
+       help
+         Sets various environment variables as specified by files
+         in the given directory
+
+config SOFTLIMIT
+       bool "softlimit"
+       help
+         Sets soft resource limits as specified by options
+
+endmenu
diff --git a/runit/Kbuild b/runit/Kbuild
new file mode 100644 (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..fcac8ee
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+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>
+
+// Must match constants in chpst_main!
+#define OPT_verbose  (option_mask32 & 0x2000)
+#define OPT_pgrp     (option_mask32 & 0x4000)
+#define OPT_nostdin  (option_mask32 & 0x8000)
+#define OPT_nostdout (option_mask32 & 0x10000)
+#define OPT_nostderr (option_mask32 & 0x20000)
+
+struct globals {
+       char *set_user;
+       char *env_user;
+       const char *env_dir;
+       const char *root;
+       long limitd; /* limitX are initialized to -2 */
+       long limits;
+       long limitl;
+       long limita;
+       long limito;
+       long limitp;
+       long limitf;
+       long limitc;
+       long limitr;
+       long limitt;
+       int nicelvl;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define set_user (G.set_user)
+#define env_user (G.env_user)
+#define env_dir  (G.env_dir )
+#define root     (G.root    )
+#define limitd   (G.limitd  )
+#define limits   (G.limits  )
+#define limitl   (G.limitl  )
+#define limita   (G.limita  )
+#define limito   (G.limito  )
+#define limitp   (G.limitp  )
+#define limitf   (G.limitf  )
+#define limitc   (G.limitc  )
+#define limitr   (G.limitr  )
+#define limitt   (G.limitt  )
+#define nicelvl  (G.nicelvl )
+#define INIT_G() do { \
+       long *p = &limitd; \
+       do *p++ = -2; while (p <= &limitt); \
+} while (0)
+
+static void suidgid(char *user)
+{
+       struct bb_uidgid_t ugid;
+
+       if (!get_uidgid(&ugid, user, 1)) {
+               bb_error_msg_and_die("unknown user/group: %s", user);
+       }
+       if (setgroups(1, &ugid.gid) == -1)
+               bb_perror_msg_and_die("setgroups");
+       xsetgid(ugid.gid);
+       xsetuid(ugid.uid);
+}
+
+static void euidgid(char *user)
+{
+       struct bb_uidgid_t ugid;
+
+       if (!get_uidgid(&ugid, user, 1)) {
+               bb_error_msg_and_die("unknown user/group: %s", user);
+       }
+       xsetenv("GID", utoa(ugid.gid));
+       xsetenv("UID", utoa(ugid.uid));
+}
+
+static void edir(const char *directory_name)
+{
+       int wdir;
+       DIR *dir;
+       struct dirent *d;
+       int fd;
+
+       wdir = xopen(".", O_RDONLY | O_NDELAY);
+       xchdir(directory_name);
+       dir = opendir(".");
+       if (!dir)
+               bb_perror_msg_and_die("opendir %s", directory_name);
+       for (;;) {
+               errno = 0;
+               d = readdir(dir);
+               if (!d) {
+                       if (errno)
+                               bb_perror_msg_and_die("readdir %s",
+                                               directory_name);
+                       break;
+               }
+               if (d->d_name[0] == '.')
+                       continue;
+               fd = open(d->d_name, O_RDONLY | O_NDELAY);
+               if (fd < 0) {
+                       if ((errno == EISDIR) && env_dir) {
+                               if (OPT_verbose)
+                                       bb_perror_msg("warning: %s/%s is a directory",
+                                               directory_name, d->d_name);
+                               continue;
+                       } else
+                               bb_perror_msg_and_die("open %s/%s",
+                                               directory_name, d->d_name);
+               }
+               if (fd >= 0) {
+                       char buf[256];
+                       char *tail;
+                       int size;
+
+                       size = safe_read(fd, buf, sizeof(buf)-1);
+                       if (size < 0)
+                               bb_perror_msg_and_die("read %s/%s",
+                                               directory_name, d->d_name);
+                       if (size == 0) {
+                               unsetenv(d->d_name);
+                               continue;
+                       }
+                       buf[size] = '\n';
+                       tail = memchr(buf, '\n', sizeof(buf));
+                       /* skip trailing whitespace */;
+                       while (1) {
+                               if (tail[0] == ' ') tail[0] = '\0';
+                               if (tail[0] == '\t') tail[0] = '\0';
+                               if (tail[0] == '\n') tail[0] = '\0';
+                               if (tail == buf) break;
+                               tail--;
+                       }
+                       xsetenv(d->d_name, buf);
+               }
+       }
+       closedir(dir);
+       if (fchdir(wdir) == -1)
+               bb_perror_msg_and_die("fchdir");
+       close(wdir);
+}
+
+static void limit(int what, long l)
+{
+       struct rlimit r;
+
+       /* 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");
+}
+
+static void slimit(void)
+{
+       if (limitd >= -1) {
+#ifdef RLIMIT_DATA
+               limit(RLIMIT_DATA, limitd);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "DATA");
+#endif
+       }
+       if (limits >= -1) {
+#ifdef RLIMIT_STACK
+               limit(RLIMIT_STACK, limits);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "STACK");
+#endif
+       }
+       if (limitl >= -1) {
+#ifdef RLIMIT_MEMLOCK
+               limit(RLIMIT_MEMLOCK, limitl);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "MEMLOCK");
+#endif
+       }
+       if (limita >= -1) {
+#ifdef RLIMIT_VMEM
+               limit(RLIMIT_VMEM, limita);
+#else
+#ifdef RLIMIT_AS
+               limit(RLIMIT_AS, limita);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "VMEM");
+#endif
+#endif
+       }
+       if (limito >= -1) {
+#ifdef RLIMIT_NOFILE
+               limit(RLIMIT_NOFILE, limito);
+#else
+#ifdef RLIMIT_OFILE
+               limit(RLIMIT_OFILE, limito);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "NOFILE");
+#endif
+#endif
+       }
+       if (limitp >= -1) {
+#ifdef RLIMIT_NPROC
+               limit(RLIMIT_NPROC, limitp);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "NPROC");
+#endif
+       }
+       if (limitf >= -1) {
+#ifdef RLIMIT_FSIZE
+               limit(RLIMIT_FSIZE, limitf);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "FSIZE");
+#endif
+       }
+       if (limitc >= -1) {
+#ifdef RLIMIT_CORE
+               limit(RLIMIT_CORE, limitc);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "CORE");
+#endif
+       }
+       if (limitr >= -1) {
+#ifdef RLIMIT_RSS
+               limit(RLIMIT_RSS, limitr);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "RSS");
+#endif
+       }
+       if (limitt >= -1) {
+#ifdef RLIMIT_CPU
+               limit(RLIMIT_CPU, limitt);
+#else
+               if (OPT_verbose)
+                       bb_error_msg("system does not support RLIMIT_%s",
+                               "CPU");
+#endif
+       }
+}
+
+/* argv[0] */
+static void setuidgid(int, char **) ATTRIBUTE_NORETURN;
+static void envuidgid(int, char **) ATTRIBUTE_NORETURN;
+static void envdir(int, char **) ATTRIBUTE_NORETURN;
+static void softlimit(int, char **) ATTRIBUTE_NORETURN;
+
+int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chpst_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       INIT_G();
+
+       if (applet_name[3] == 'd') envdir(argc, argv);
+       if (applet_name[1] == 'o') softlimit(argc, argv);
+       if (applet_name[0] == 's') setuidgid(argc, argv);
+       if (applet_name[0] == 'e') envuidgid(argc, argv);
+       // otherwise we are chpst
+
+       {
+               char *m,*d,*o,*p,*f,*c,*r,*t,*n;
+               getopt32(argv, "+u:U:e:m:d:o:p:f:c:r:t:/:n:vP012",
+                               &set_user,&env_user,&env_dir,
+                               &m,&d,&o,&p,&f,&c,&r,&t,&root,&n);
+               // if (option_mask32 & 0x1) // -u
+               // if (option_mask32 & 0x2) // -U
+               // if (option_mask32 & 0x4) // -e
+               if (option_mask32 & 0x8) limits = limitl = limita = limitd = xatoul(m); // -m
+               if (option_mask32 & 0x10) limitd = xatoul(d); // -d
+               if (option_mask32 & 0x20) limito = xatoul(o); // -o
+               if (option_mask32 & 0x40) limitp = xatoul(p); // -p
+               if (option_mask32 & 0x80) limitf = xatoul(f); // -f
+               if (option_mask32 & 0x100) limitc = xatoul(c); // -c
+               if (option_mask32 & 0x200) limitr = xatoul(r); // -r
+               if (option_mask32 & 0x400) limitt = xatoul(t); // -t
+               // if (option_mask32 & 0x800) // -/
+               if (option_mask32 & 0x1000) nicelvl = xatoi(n); // -n
+               // The below consts should match #defines at top!
+               //if (option_mask32 & 0x2000) OPT_verbose = 1; // -v
+               //if (option_mask32 & 0x4000) OPT_pgrp = 1; // -P
+               //if (option_mask32 & 0x8000) OPT_nostdin = 1; // -0
+               //if (option_mask32 & 0x10000) OPT_nostdout = 1; // -1
+               //if (option_mask32 & 0x20000) OPT_nostderr = 1; // -2
+       }
+       argv += optind;
+       if (!argv || !*argv) bb_show_usage();
+
+       if (OPT_pgrp) setsid();
+       if (env_dir) edir(env_dir);
+       if (root) {
+               xchdir(root);
+               xchroot(".");
+       }
+       slimit();
+       if (nicelvl) {
+               errno = 0;
+               if (nice(nicelvl) == -1)
+                       bb_perror_msg_and_die("nice");
+       }
+       if (env_user) euidgid(env_user);
+       if (set_user) suidgid(set_user);
+       if (OPT_nostdin) close(0);
+       if (OPT_nostdout) close(1);
+       if (OPT_nostderr) close(2);
+       BB_EXECVP(argv[0], argv);
+       bb_perror_msg_and_die("exec %s", argv[0]);
+}
+
+static void setuidgid(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       const char *account;
+
+       account = *++argv;
+       if (!account) bb_show_usage();
+       if (!*++argv) bb_show_usage();
+       suidgid((char*)account);
+       BB_EXECVP(argv[0], argv);
+       bb_perror_msg_and_die("exec %s", argv[0]);
+}
+
+static void envuidgid(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       const char *account;
+
+       account = *++argv;
+       if (!account) bb_show_usage();
+       if (!*++argv) bb_show_usage();
+       euidgid((char*)account);
+       BB_EXECVP(argv[0], argv);
+       bb_perror_msg_and_die("exec %s", argv[0]);
+}
+
+static void envdir(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       const char *dir;
+
+       dir = *++argv;
+       if (!dir) bb_show_usage();
+       if (!*++argv) bb_show_usage();
+       edir(dir);
+       BB_EXECVP(argv[0], argv);
+       bb_perror_msg_and_die("exec %s", argv[0]);
+}
+
+static void softlimit(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       char *a,*c,*d,*f,*l,*m,*o,*p,*r,*s,*t;
+       getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:",
+                       &a,&c,&d,&f,&l,&m,&o,&p,&r,&s,&t);
+       if (option_mask32 & 0x001) limita = xatoul(a); // -a
+       if (option_mask32 & 0x002) limitc = xatoul(c); // -c
+       if (option_mask32 & 0x004) limitd = xatoul(d); // -d
+       if (option_mask32 & 0x008) limitf = xatoul(f); // -f
+       if (option_mask32 & 0x010) limitl = xatoul(l); // -l
+       if (option_mask32 & 0x020) limits = limitl = limita = limitd = xatoul(m); // -m
+       if (option_mask32 & 0x040) limito = xatoul(o); // -o
+       if (option_mask32 & 0x080) limitp = xatoul(p); // -p
+       if (option_mask32 & 0x100) limitr = xatoul(r); // -r
+       if (option_mask32 & 0x200) limits = xatoul(s); // -s
+       if (option_mask32 & 0x400) limitt = xatoul(t); // -t
+       argv += optind;
+       if (!argv[0]) bb_show_usage();
+       slimit();
+       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..4b94820
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+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.
+*/
+
+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 ATTRIBUTE_PACKED;
+       uint32_t time_nsec_be32 ATTRIBUTE_PACKED;
+       uint32_t pid_le32 ATTRIBUTE_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];
+};
diff --git a/runit/runsv.c b/runit/runsv.c
new file mode 100644 (file)
index 0000000..2ab034a
--- /dev/null
@@ -0,0 +1,655 @@
+/*
+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 ATTRIBUTE_UNUSED)
+{
+       write(selfpipe.wr, "", 1);
+}
+
+static void s_term(int sig_no ATTRIBUTE_UNUSED)
+{
+       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)
+{
+       int pid;
+       int w;
+       char a[10];
+       struct stat st;
+       char *prog[2];
+
+       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/?");
+                               prog[0] = a;
+                               prog[1] = NULL;
+                               execv(a, prog);
+                               fatal_cannot("run control/?");
+                       }
+                       /* parent */
+                       while (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;
+       char *run[2];
+
+       if (s->state == S_FINISH)
+               run[0] = (char*)"./finish";
+       else {
+               run[0] = (char*)"./run";
+               custom(s, 'u');
+       }
+       run[1] = NULL;
+
+       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);
+                       }
+               }
+               bb_signals(0
+                       + (1 << SIGCHLD)
+                       + (1 << SIGTERM)
+                       , SIG_DFL);
+               sig_unblock(SIGCHLD);
+               sig_unblock(SIGTERM);
+               execvp(*run, run);
+               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);
+               if (s->ctrl & C_PAUSE)
+                       s->ctrl &= ~C_PAUSE;
+               update_status(s);
+               break;
+       case 'o': /* once */
+               s->want = W_DOWN;
+               update_status(s);
+               if (!s->pid)
+                       startservice(s);
+               break;
+       case 'a': /* sig alarm */
+               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 ATTRIBUTE_UNUSED, 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(1 << SIGCHLD, s_child);
+       sig_block(SIGTERM);
+       bb_signals_recursive(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 (;;) {
+                       int 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(0);
+                       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..32e4764
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+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
+
+struct service {
+       dev_t dev;
+       ino_t ino;
+       pid_t pid;
+       smallint isgone;
+};
+
+struct globals {
+       struct service *sv;
+       char *svdir;
+       char *rplog;
+       int svnum;
+       int rploglen;
+       struct fd_pair logpipe;
+       struct pollfd pfd[1];
+       unsigned stamplog;
+       smallint check; /* = 1; */
+       smallint exitsoon;
+       smallint set_pgrp;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define sv        (G.sv        )
+#define svdir     (G.svdir     )
+#define rplog     (G.rplog     )
+#define svnum     (G.svnum     )
+#define rploglen  (G.rploglen  )
+#define logpipe   (G.logpipe   )
+#define pfd       (G.pfd       )
+#define stamplog  (G.stamplog  )
+#define check     (G.check     )
+#define exitsoon  (G.exitsoon  )
+#define set_pgrp  (G.set_pgrp  )
+#define INIT_G() do { \
+       check = 1; \
+} 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);
+}
+static void warnx(const char *m1)
+{
+       warn3x(m1, "", "");
+}
+
+static void s_term(int sig_no ATTRIBUTE_UNUSED)
+{
+       exitsoon = 1;
+}
+static void s_hangup(int sig_no ATTRIBUTE_UNUSED)
+{
+       exitsoon = 2;
+}
+
+static void runsv(int no, const char *name)
+{
+       pid_t pid;
+       char *prog[3];
+
+       prog[0] = (char*)"runsv";
+       prog[1] = (char*)name;
+       prog[2] = NULL;
+
+       pid = vfork();
+
+       if (pid == -1) {
+               warn2_cannot("vfork", "");
+               return;
+       }
+       if (pid == 0) {
+               /* child */
+               if (set_pgrp)
+                       setsid();
+               bb_signals(0
+                       + (1 << SIGHUP)
+                       + (1 << SIGTERM)
+                       , SIG_DFL);
+               execvp(prog[0], prog);
+               fatal2_cannot("start runsv ", name);
+       }
+       sv[no].pid = pid;
+}
+
+static void runsvdir(void)
+{
+       DIR *dir;
+       direntry *d;
+       int i;
+       struct stat s;
+
+       dir = opendir(".");
+       if (!dir) {
+               warn2_cannot("open directory ", svdir);
+               return;
+       }
+       for (i = 0; i < svnum; i++)
+               sv[i].isgone = 1;
+       errno = 0;
+       while ((d = readdir(dir))) {
+               if (d->d_name[0] == '.')
+                       continue;
+               if (stat(d->d_name, &s) == -1) {
+                       warn2_cannot("stat ", d->d_name);
+                       errno = 0;
+                       continue;
+               }
+               if (!S_ISDIR(s.st_mode))
+                       continue;
+               for (i = 0; i < svnum; i++) {
+                       if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) {
+                               sv[i].isgone = 0;
+                               if (!sv[i].pid)
+                                       runsv(i, d->d_name);
+                               break;
+                       }
+               }
+               if (i == svnum) {
+                       /* new service */
+                       struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
+                       if (!svnew) {
+                               warn3x("cannot start runsv ", d->d_name,
+                                               " too many services");
+                               continue;
+                       }
+                       sv = svnew;
+                       svnum++;
+                       memset(&sv[i], 0, sizeof(sv[i]));
+                       sv[i].ino = s.st_ino;
+                       sv[i].dev = s.st_dev;
+                       /*sv[i].pid = 0;*/
+                       /*sv[i].isgone = 0;*/
+                       runsv(i, d->d_name);
+                       check = 1;
+               }
+       }
+       if (errno) {
+               warn2_cannot("read directory ", svdir);
+               closedir(dir);
+               check = 1;
+               return;
+       }
+       closedir(dir);
+
+       /* SIGTERM removed runsv's */
+       for (i = 0; i < svnum; i++) {
+               if (!sv[i].isgone)
+                       continue;
+               if (sv[i].pid)
+                       kill(sv[i].pid, SIGTERM);
+               sv[i] = sv[--svnum];
+               check = 1;
+       }
+}
+
+static int setup_log(void)
+{
+       rploglen = strlen(rplog);
+       if (rploglen < 7) {
+               warnx("log must have at least seven characters");
+               return 0;
+       }
+       if (piped_pair(logpipe)) {
+               warnx("cannot create pipe for log");
+               return -1;
+       }
+       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");
+               return -1;
+       }
+       pfd[0].fd = logpipe.rd;
+       pfd[0].events = POLLIN;
+       stamplog = monotonic_sec();
+       return 1;
+}
+
+int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int runsvdir_main(int argc ATTRIBUTE_UNUSED, 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;
+       int pid;
+       unsigned deadline;
+       unsigned now;
+       unsigned stampcheck;
+       char ch;
+       int i;
+
+       INIT_G();
+
+       argv++;
+       if (!*argv)
+               bb_show_usage();
+       if (argv[0][0] == '-') {
+               switch (argv[0][1]) {
+               case 'P': set_pgrp = 1;
+               case '-': ++argv;
+               }
+               if (!*argv)
+                       bb_show_usage();
+       }
+
+       bb_signals_recursive(1 << SIGTERM, s_term);
+       bb_signals_recursive(1 << SIGHUP, s_hangup);
+       svdir = *argv++;
+       if (argv && *argv) {
+               rplog = *argv;
+               if (setup_log() != 1) {
+                       rplog = 0;
+                       warnx("log service disabled");
+               }
+       }
+       curdir = open_read(".");
+       if (curdir == -1)
+               fatal2_cannot("open current directory", "");
+       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 gone */
+                                       sv[i].pid = 0;
+                                       check = 1;
+                                       break;
+                               }
+                       }
+               }
+
+               now = monotonic_sec();
+               if ((int)(now - stampcheck) >= 0) {
+                       /* wait at least a second */
+                       stampcheck = now + 1;
+
+                       if (stat(svdir, &s) != -1) {
+                               if (check || 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;
+                                               check = 0;
+                                               //if (now <= mtime)
+                                               //      sleep(1);
+                                               runsvdir();
+                                               while (fchdir(curdir) == -1) {
+                                                       warn2_cannot("change directory, pausing", "");
+                                                       sleep(5);
+                                               }
+                                       } else
+                                               warn2_cannot("change directory to ", svdir);
+                               }
+                       } else
+                               warn2_cannot("stat ", svdir);
+               }
+
+               if (rplog) {
+                       if ((int)(now - stamplog) >= 0) {
+                               write(logpipe.wr, ".", 1);
+                               stamplog = now + 900;
+                       }
+               }
+
+               pfd[0].revents = 0;
+               sig_block(SIGCHLD);
+               deadline = (check ? 1 : 5);
+               if (rplog)
+                       poll(pfd, 1, deadline*1000);
+               else
+                       sleep(deadline);
+               sig_unblock(SIGCHLD);
+
+               if (pfd[0].revents & POLLIN) {
+                       while (read(logpipe.rd, &ch, 1) > 0) {
+                               if (ch) {
+                                       for (i = 6; i < rploglen; i++)
+                                               rplog[i-1] = rplog[i];
+                                       rplog[rploglen-1] = ch;
+                               }
+                       }
+               }
+
+               switch (exitsoon) {
+               case 1:
+                       _exit(0);
+               case 2:
+                       for (i = 0; i < svnum; i++)
+                               if (sv[i].pid)
+                                       kill(sv[i].pid, SIGTERM);
+                       _exit(111);
+               }
+       }
+       /* not reached */
+       return 0;
+}
diff --git a/runit/sv.c b/runit/sv.c
new file mode 100644 (file)
index 0000000..d5a9bd9
--- /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) ATTRIBUTE_NORETURN;
+static void fatal_cannot(const char *m1)
+{
+       bb_perror_msg("fatal: cannot %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 ATTRIBUTE_UNUSED)
+{
+       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 = "/var/service/";
+       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(0) + 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(0) + 0x400000000000000aULL;
+       }
+       return rc > 99 ? 99 : rc;
+}
diff --git a/runit/svlogd.c b/runit/svlogd.c
new file mode 100644 (file)
index 0000000..db3d4c5
--- /dev/null
@@ -0,0 +1,1054 @@
+/*
+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"cannot %s", m0);
+       sleep(3);
+}
+static void pause2cannot(const char *m0, const char *m1)
+{
+       bb_perror_msg(PAUSE"cannot %s %s", m0, m1);
+       sleep(3);
+}
+
+static char* wstrdup(const char *str)
+{
+       char *s;
+       while (!(s = strdup(str)))
+               pause_nomem();
+       return s;
+}
+
+/*** 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 */
+               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"cannot %s processor %s", "open state for", ld->name);
+                       close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
+                       fd = xopen("state", O_RDONLY|O_NDELAY);
+               }
+               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"cannot %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"cannot unlink: %s/%s", ld->name, ld->fnsave);
+       while (rename("newstate", "state") == -1)
+               pause2cannot("rename state", ld->name);
+       if (verbose)
+               bb_error_msg(INFO"processed: %s/%s", ld->name, f);
+       while (fchdir(fdwdir) == -1)
+               pause1cannot("change to initial working directory");
+       return 1;
+}
+
+static void rmoldest(struct logdir *ld)
+{
+       DIR *d;
+       struct dirent *f;
+       char oldest[FMT_PTIME];
+       int n = 0;
+
+       oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
+       while (!(d = opendir(".")))
+               pause2cannot("open directory, want rotate", ld->name);
+       errno = 0;
+       while ((f = readdir(d))) {
+               if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
+                       if (f->d_name[26] == 't') {
+                               if (unlink(f->d_name) == -1)
+                                       warn2("cannot unlink processor leftover", f->d_name);
+                       } else {
+                               ++n;
+                               if (strcmp(f->d_name, oldest) < 0)
+                                       memcpy(oldest, f->d_name, 27);
+                       }
+                       errno = 0;
+               }
+       }
+       if (errno)
+               warn2("cannot read directory", ld->name);
+       closedir(d);
+
+       if (ld->nmax && (n > ld->nmax)) {
+               if (verbose)
+                       bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
+               if ((*oldest == '@') && (unlink(oldest) == -1))
+                       warn2("cannot unlink oldest logfile", ld->name);
+       }
+}
+
+static unsigned rotate(struct logdir *ld)
+{
+       struct stat st;
+       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("cannot read directory, want remove old logfile",
+                                       ld->name);
+                       closedir(d);
+                       errno = ENOSPC;
+                       if (j > ld->nmin) {
+                               if (*oldest == '@') {
+                                       bb_error_msg(WARNING"out of disk space, delete: %s/%s",
+                                                       ld->name, oldest);
+                                       errno = 0;
+                                       if (unlink(oldest) == -1) {
+                                               warn2("cannot unlink oldest logfile", ld->name);
+                                               errno = ENOSPC;
+                                       }
+                                       while (fchdir(fdwdir) == -1)
+                                               pause1cannot("change to initial working directory");
+                               }
+                       }
+               }
+               if (errno)
+                       pause2cannot("write to current", ld->name);
+       }
+
+       ld->size += i;
+       if (ld->sizemax)
+               if (s[i-1] == '\n')
+                       if (ld->size >= (ld->sizemax - linemax))
+                               rotate(ld);
+       return i;
+}
+
+static void logdir_close(struct logdir *ld)
+{
+       if (ld->fddir == -1)
+               return;
+       if (verbose)
+               bb_error_msg(INFO"close: %s", ld->name);
+       close(ld->fddir);
+       ld->fddir = -1;
+       if (ld->fdcur == -1)
+               return; /* impossible */
+       while (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("cannot open log directory", (char*)fn);
+               return 0;
+       }
+       close_on_exec_on(ld->fddir);
+       if (fchdir(ld->fddir) == -1) {
+               logdir_close(ld);
+               warn2("cannot change directory", (char*)fn);
+               return 0;
+       }
+       ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
+       if ((ld->fdlock == -1)
+        || (lock_exnb(ld->fdlock) == -1)
+       ) {
+               logdir_close(ld);
+               warn2("cannot lock directory", (char*)fn);
+               while (fchdir(fdwdir) == -1)
+                       pause1cannot("change to initial working directory");
+               return 0;
+       }
+       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("cannot stat current", ld->name);
+                       while (fchdir(fdwdir) == -1)
+                               pause1cannot("change to initial working directory");
+                       return 0;
+               }
+       }
+       while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
+               pause2cannot("open current", ld->name);
+       /* 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(0, s, len);
+               if (i >= 0)
+                       break;
+               if (errno == EINTR)
+                       continue;
+               if (errno != EAGAIN) {
+                       warn("cannot 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 ATTRIBUTE_UNUSED)
+{
+       if (verbose)
+               bb_error_msg(INFO"sig%s received", "term");
+       exitasap = 1;
+}
+
+static void sig_child_handler(int sig_no ATTRIBUTE_UNUSED)
+{
+       int pid, 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 ATTRIBUTE_UNUSED)
+{
+       if (verbose)
+               bb_error_msg(INFO"sig%s received", "alarm");
+       rotateasap = 1;
+}
+
+static void sig_hangup_handler(int sig_no ATTRIBUTE_UNUSED)
+{
+       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(1 << SIGTERM, sig_term_handler);
+       bb_signals_recursive(1 << SIGCHLD, sig_child_handler);
+       bb_signals_recursive(1 << SIGALRM, sig_alarm_handler);
+       bb_signals_recursive(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(2, 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(2, 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..1d7bc2c
--- /dev/null
@@ -0,0 +1,143 @@
+# This is completely unsupported.
+# 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)
+
+default: busybox
+include .config
+ifdef CONFIG_FEATURE_COMPRESS_USAGE
+usage_stuff = include/usage_compressed.h
+endif
+
+# 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 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 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:=
+
+include Makefile.flags
+ifndef BB_VER
+BB_VER:=""
+endif
+
+CPPFLAGS+= -D"KBUILD_STR(s)=\#s" #-Q
+
+HOSTCC = gcc
+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
+
+WHOLE_PROGRAM:=$(call cc-option,-fwhole-program,)
+busybox: $(usage_stuff)
+       $(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) --combine $(WHOLE_PROGRAM) \
+               -funit-at-a-time -Wno-error -std=gnu99  \
+               -o $(@)_unstripped $(lib-all-y:.o=.c) \
+               -Wl,--start-group -lcrypt -lm -Wl,--end-group
+       cp -f $(@)_unstripped $@
+       -$(STRIP) -s -R .note -R .comment -R .version $@
+
+applets/usage:
+       $(HOSTCC) -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer      -I$(srctree)/include -o applets/usage applets/usage.c
+include/usage_compressed.h: $(srctree)/include/usage.h applets/usage
+       $(srctree)/applets/usage_compressed include/usage_compressed.h applets
+
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
new file mode 100644 (file)
index 0000000..ddefea5
--- /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..763e2f2
--- /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..e1e0ba4
--- /dev/null
@@ -0,0 +1,165 @@
+# Backward compatibility - to be removed...
+extra-y        += $(EXTRA_TARGETS)
+# Figure out what we need to build from the various variables
+# ===========================================================================
+
+# When an object is listed to be built compiled-in and modular,
+# only build the compiled-in version
+
+obj-m := $(filter-out $(obj-y),$(obj-m))
+
+# Libraries are always collected in one lib file.
+# Filter out objects already built-in
+
+lib-y := $(filter-out $(obj-y), $(sort $(lib-y) $(lib-m)))
+
+
+# Handle objects in subdirs
+# ---------------------------------------------------------------------------
+# o if we encounter foo/ in $(obj-y), replace it by foo/built-in.o
+#   and add the directory to the list of dirs to descend into: $(subdir-y)
+# o if we encounter foo/ in $(obj-m), remove it from $(obj-m)
+#   and add the directory to the list of dirs to descend into: $(subdir-m)
+
+__subdir-y     := $(patsubst %/,%,$(filter %/, $(obj-y)))
+subdir-y       += $(__subdir-y)
+__subdir-m     := $(patsubst %/,%,$(filter %/, $(obj-m)))
+subdir-m       += $(__subdir-m)
+obj-y          := $(patsubst %/, %/built-in.o, $(obj-y))
+obj-m          := $(filter-out %/, $(obj-m))
+
+# Subdirectories we need to descend into
+
+subdir-ym      := $(sort $(subdir-y) $(subdir-m))
+
+# if $(foo-objs) exists, foo.o is a composite object
+multi-used-y := $(sort $(foreach m,$(obj-y), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))), $(m))))
+multi-used-m := $(sort $(foreach m,$(obj-m), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))), $(m))))
+multi-used   := $(multi-used-y) $(multi-used-m)
+single-used-m := $(sort $(filter-out $(multi-used-m),$(obj-m)))
+
+# Build list of the parts of our composite objects, our composite
+# objects depend on those (obviously)
+multi-objs-y := $(foreach m, $(multi-used-y), $($(m:.o=-objs)) $($(m:.o=-y)))
+multi-objs-m := $(foreach m, $(multi-used-m), $($(m:.o=-objs)) $($(m:.o=-y)))
+multi-objs   := $(multi-objs-y) $(multi-objs-m)
+
+# $(subdir-obj-y) is the list of objects in $(obj-y) which do not live
+# in the local directory
+subdir-obj-y := $(foreach o,$(obj-y),$(if $(filter-out $(o),$(notdir $(o))),$(o)))
+
+# $(obj-dirs) is a list of directories that contain object files
+obj-dirs := $(dir $(multi-objs) $(subdir-obj-y))
+
+# Replace multi-part objects by their individual parts, look at local dir only
+real-objs-y := $(foreach m, $(filter-out $(subdir-obj-y), $(obj-y)), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m))) $(extra-y)
+real-objs-m := $(foreach m, $(obj-m), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m)))
+
+# Add subdir path
+
+extra-y                := $(addprefix $(obj)/,$(extra-y))
+always         := $(addprefix $(obj)/,$(always))
+targets                := $(addprefix $(obj)/,$(targets))
+obj-y          := $(addprefix $(obj)/,$(obj-y))
+obj-m          := $(addprefix $(obj)/,$(obj-m))
+lib-y          := $(addprefix $(obj)/,$(lib-y))
+subdir-obj-y   := $(addprefix $(obj)/,$(subdir-obj-y))
+real-objs-y    := $(addprefix $(obj)/,$(real-objs-y))
+real-objs-m    := $(addprefix $(obj)/,$(real-objs-m))
+single-used-m  := $(addprefix $(obj)/,$(single-used-m))
+multi-used-y   := $(addprefix $(obj)/,$(multi-used-y))
+multi-used-m   := $(addprefix $(obj)/,$(multi-used-m))
+multi-objs-y   := $(addprefix $(obj)/,$(multi-objs-y))
+multi-objs-m   := $(addprefix $(obj)/,$(multi-objs-m))
+subdir-ym      := $(addprefix $(obj)/,$(subdir-ym))
+obj-dirs       := $(addprefix $(obj)/,$(obj-dirs))
+
+# These flags are needed for modversions and compiling, so we define them here
+# already
+# $(modname_flags) #defines KBUILD_MODNAME as the name of the module it will
+# end up in (or would, if it gets compiled in)
+# Note: It's possible that one object gets potentially linked into more
+#       than one module. In that case KBUILD_MODNAME will be set to foo_bar,
+#       where foo and bar are the name of the modules.
+name-fix = $(subst $(comma),_,$(subst -,_,$1))
+basename_flags = -D"KBUILD_BASENAME=KBUILD_STR($(call name-fix,$(*F)))"
+modname_flags  = $(if $(filter 1,$(words $(modname))),\
+                 -D"KBUILD_MODNAME=KBUILD_STR($(call name-fix,$(modname)))")
+
+_c_flags       = $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F).o)
+_a_flags       = $(AFLAGS) $(EXTRA_AFLAGS) $(AFLAGS_$(*F).o)
+_cpp_flags     = $(CPPFLAGS) $(EXTRA_CPPFLAGS) $(CPPFLAGS_$(@F))
+
+# If building the kernel in a separate objtree expand all occurrences
+# of -Idir to -I$(srctree)/dir except for absolute paths (starting with '/').
+
+ifeq ($(KBUILD_SRC),)
+__c_flags      = $(_c_flags)
+__a_flags      = $(_a_flags)
+__cpp_flags     = $(_cpp_flags)
+else
+
+# -I$(obj) locates generated .h files
+# $(call addtree,-I$(obj)) locates .h files in srctree, from generated .c files
+#   and locates generated .h files
+# FIXME: Replace both with specific CFLAGS* statements in the makefiles
+__c_flags      = $(call addtree,-I$(obj)) $(call flags,_c_flags)
+__a_flags      =                          $(call flags,_a_flags)
+__cpp_flags     =                          $(call flags,_cpp_flags)
+endif
+
+c_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(CPPFLAGS) \
+                $(__c_flags) $(modkern_cflags) \
+                -D"KBUILD_STR(s)=\#s" $(basename_flags) $(modname_flags)
+
+a_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(CPPFLAGS) \
+                $(__a_flags) $(modkern_aflags)
+
+cpp_flags      = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(__cpp_flags)
+
+ld_flags       = $(LDFLAGS) $(EXTRA_LDFLAGS)
+
+# Finds the multi-part object the current object will be linked into
+modname-multi = $(sort $(foreach m,$(multi-used),\
+               $(if $(filter $(subst $(obj)/,,$*.o), $($(m:.o=-objs)) $($(m:.o=-y))),$(m:.o=))))
+
+# Shipped files
+# ===========================================================================
+
+quiet_cmd_shipped = SHIPPED $@
+cmd_shipped = cat $< > $@
+
+$(obj)/%:: $(src)/%_shipped
+       $(call cmd,shipped)
+
+# Commands useful for building a boot image
+# ===========================================================================
+#
+#      Use as following:
+#
+#      target: source(s) FORCE
+#              $(if_changed,ld/objcopy/gzip)
+#
+#      and add target to EXTRA_TARGETS so that we know we have to
+#      read in the saved command line
+
+# Linking
+# ---------------------------------------------------------------------------
+
+quiet_cmd_ld = LD      $@
+cmd_ld = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_$(@F)) \
+              $(filter-out FORCE,$^) -o $@
+
+# Objcopy
+# ---------------------------------------------------------------------------
+
+quiet_cmd_objcopy = OBJCOPY $@
+cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
+
+# Gzip
+# ---------------------------------------------------------------------------
+
+quiet_cmd_gzip = GZIP    $@
+cmd_gzip = gzip -f -9 < $< > $@
+
+
diff --git a/scripts/basic/Makefile b/scripts/basic/Makefile
new file mode 100644 (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..459c452
--- /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..f6608af
--- /dev/null
@@ -0,0 +1,70 @@
+#!/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
+
+if len(sys.argv) != 3:
+    sys.stderr.write("usage: %s file1 file2\n" % sys.argv[0])
+    sys.exit(-1)
+
+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)
+        sys.exit(-1)
+
+def getsizes(file):
+    sym = {}
+    for l in os.popen("nm --size-sort " + file).readlines():
+        size, type, name = l[:-1].split()
+        if type in "tTdDbBrR":
+            if "." in name: name = "static." + name.split(".")[0]
+            sym[name] = sym.get(name, 0) + int(size, 16)
+    for l in os.popen("readelf -S " + file).readlines():
+        x = l.split()
+        if len(x)<6 or x[1] != ".rodata": continue
+        sym[".rodata"] = int(x[5], 16)
+    return sym
+
+old = getsizes(sys.argv[1])
+new = getsizes(sys.argv[2])
+grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
+delta, common = [], {}
+
+for a in old:
+    if a in new:
+        common[a] = 1
+
+for name in old:
+    if name not in common:
+        remove += 1
+        down += old[name]
+        delta.append((-old[name], name))
+
+for name in new:
+    if name not in common:
+        add += 1
+        up += new[name]
+        delta.append((new[name], name))
+
+for name in common:
+        d = new.get(name, 0) - old.get(name, 0)
+        if d>0: grow, up = grow+1, up+d
+        if d<0: shrink, down = shrink+1, down-d
+        delta.append((d, name))
+
+delta.sort()
+delta.reverse()
+
+print "%-48s %7s %7s %+7s" % ("function", "old", "new", "delta")
+for d, n in delta:
+    if d: print "%-48s %7s %7s %+7d" % (n, old.get(n,"-"), new.get(n,"-"), d)
+print "-"*78
+total="(add/remove: %s/%s grow/shrink: %s/%s up/down: %s/%s)%%sTotal: %s bytes"\
+    % (add, remove, grow, shrink, up, -down, up-down)
+print total % (" "*(80-len(total)))
diff --git a/scripts/checkhelp.awk b/scripts/checkhelp.awk
new file mode 100755 (executable)
index 0000000..4bb4996
--- /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 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..0ca4e72
--- /dev/null
@@ -0,0 +1,833 @@
+#
+# Automatically generated make config: don't edit
+# Busybox version: 1.10.0.svn
+# Fri Mar 21 21:33:40 2008
+#
+CONFIG_HAVE_DOT_CONFIG=y
+
+#
+# Busybox Settings
+#
+
+#
+# General Configuration
+#
+CONFIG_NITPICK=y
+# CONFIG_DESKTOP 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_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
+
+#
+# Debugging Options
+#
+# CONFIG_DEBUG 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 is not set
+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
+
+#
+# Applets
+#
+
+#
+# Archival Utilities
+#
+CONFIG_AR=y
+CONFIG_FEATURE_AR_LONG_FILENAMES=y
+CONFIG_BUNZIP2=y
+CONFIG_BZIP2=y
+CONFIG_CPIO=y
+# CONFIG_DPKG is not set
+# CONFIG_DPKG_DEB is not set
+# CONFIG_FEATURE_DPKG_DEB_EXTRACT_ONLY is not set
+CONFIG_GUNZIP=y
+CONFIG_FEATURE_GUNZIP_UNCOMPRESS=y
+CONFIG_GZIP=y
+CONFIG_RPM2CPIO=y
+CONFIG_RPM=y
+CONFIG_FEATURE_RPM_BZ2=y
+CONFIG_TAR=y
+CONFIG_FEATURE_TAR_CREATE=y
+CONFIG_FEATURE_TAR_GZIP=y
+CONFIG_FEATURE_TAR_BZIP2=y
+CONFIG_FEATURE_TAR_LZMA=y
+CONFIG_FEATURE_TAR_COMPRESS=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
+
+#
+# Common options for cpio and tar
+#
+CONFIG_FEATURE_UNARCHIVE_TAPE=y
+# CONFIG_FEATURE_DEB_TAR_GZ is not set
+# CONFIG_FEATURE_DEB_TAR_BZ2 is not set
+# CONFIG_FEATURE_DEB_TAR_LZMA is not set
+
+#
+# Coreutils
+#
+CONFIG_BASENAME=y
+CONFIG_CAL=y
+CONFIG_CAT=y
+CONFIG_CATV=y
+CONFIG_CHGRP=y
+CONFIG_CHMOD=y
+CONFIG_CHOWN=y
+CONFIG_CHROOT=y
+CONFIG_CKSUM=y
+CONFIG_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_INODE=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 is not set
+CONFIG_SEQ=y
+CONFIG_SHA1SUM=y
+CONFIG_SLEEP=y
+CONFIG_FEATURE_FANCY_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_SETKEYCODES=y
+CONFIG_SETLOGCONS=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_MATH=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_DEBUG_INIT is not set
+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 is not set
+CONFIG_FEATURE_EXTRA_QUIET=y
+CONFIG_FEATURE_INIT_COREDUMPS=y
+CONFIG_FEATURE_INITRD=y
+CONFIG_HALT=y
+CONFIG_MESG=y
+
+#
+# Login/Password Management Utilities
+#
+CONFIG_FEATURE_SHADOWPASSWDS=y
+CONFIG_USE_BB_SHADOW=y
+CONFIG_USE_BB_PWD_GRP=y
+CONFIG_ADDGROUP=y
+CONFIG_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 is not set
+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_INSMOD=y
+CONFIG_FEATURE_INSMOD_VERSION_CHECKING=y
+CONFIG_FEATURE_INSMOD_KSYMOOPS_SYMBOLS=y
+CONFIG_FEATURE_INSMOD_LOADINKMEM=y
+CONFIG_FEATURE_INSMOD_LOAD_MAP=y
+CONFIG_FEATURE_INSMOD_LOAD_MAP_FULL=y
+CONFIG_RMMOD=y
+CONFIG_LSMOD=y
+CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT=y
+CONFIG_MODPROBE=y
+CONFIG_FEATURE_MODPROBE_MULTIPLE_OPTIONS=y
+CONFIG_FEATURE_MODPROBE_FANCY_ALIAS=y
+
+#
+# Options common to multiple modutils
+#
+CONFIG_FEATURE_CHECK_TAINTED_MODULE=y
+CONFIG_FEATURE_2_4_MODULES=y
+CONFIG_FEATURE_2_6_MODULES=y
+# CONFIG_FEATURE_QUERY_MODULE_INTERFACE is not set
+
+#
+# Linux System Utilities
+#
+CONFIG_DMESG=y
+CONFIG_FEATURE_DMESG_PRETTY=y
+CONFIG_FBSET=y
+CONFIG_FEATURE_FBSET_FANCY=y
+CONFIG_FEATURE_FBSET_READMODE=y
+CONFIG_FDFLUSH=y
+CONFIG_FDFORMAT=y
+CONFIG_FDISK=y
+CONFIG_FDISK_SUPPORT_LARGE_DISKS=y
+CONFIG_FEATURE_FDISK_WRITABLE=y
+# CONFIG_FEATURE_AIX_LABEL is not set
+# CONFIG_FEATURE_SGI_LABEL is not set
+# CONFIG_FEATURE_SUN_LABEL is not set
+# CONFIG_FEATURE_OSF_LABEL is not set
+CONFIG_FEATURE_FDISK_ADVANCED=y
+# CONFIG_FINDFS is not set
+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 is not set
+# CONFIG_HD is not set
+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_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 is not set
+# CONFIG_FEATURE_VOLUMEID_EXT is not set
+# CONFIG_FEATURE_VOLUMEID_REISERFS is not set
+# CONFIG_FEATURE_VOLUMEID_FAT is not set
+# CONFIG_FEATURE_VOLUMEID_HFS is not set
+# CONFIG_FEATURE_VOLUMEID_JFS is not set
+# CONFIG_FEATURE_VOLUMEID_XFS is not set
+# CONFIG_FEATURE_VOLUMEID_NTFS is not set
+# CONFIG_FEATURE_VOLUMEID_ISO9660 is not set
+# CONFIG_FEATURE_VOLUMEID_UDF is not set
+# CONFIG_FEATURE_VOLUMEID_LUKS is not set
+# CONFIG_FEATURE_VOLUMEID_LINUXSWAP is not set
+# CONFIG_FEATURE_VOLUMEID_CRAMFS is not set
+# CONFIG_FEATURE_VOLUMEID_ROMFS is not set
+# CONFIG_FEATURE_VOLUMEID_SYSV is not set
+# CONFIG_FEATURE_VOLUMEID_OCFS2 is not set
+# CONFIG_FEATURE_VOLUMEID_LINUXRAID is not set
+CONFIG_MOUNT=y
+CONFIG_FEATURE_MOUNT_FAKE=y
+CONFIG_FEATURE_MOUNT_VERBOSE=y
+# CONFIG_FEATURE_MOUNT_HELPERS is not set
+# CONFIG_FEATURE_MOUNT_LABEL is not set
+CONFIG_FEATURE_MOUNT_NFS=y
+CONFIG_FEATURE_MOUNT_CIFS=y
+CONFIG_FEATURE_MOUNT_FLAGS=y
+CONFIG_FEATURE_MOUNT_FSTAB=y
+CONFIG_PIVOT_ROOT=y
+CONFIG_RDATE=y
+CONFIG_READPROFILE=y
+# CONFIG_RTCWAKE is not set
+CONFIG_SETARCH=y
+CONFIG_SWAPONOFF=y
+CONFIG_SWITCH_ROOT=y
+CONFIG_UMOUNT=y
+CONFIG_FEATURE_UMOUNT_ALL=y
+
+#
+# Common options for mount/umount
+#
+CONFIG_FEATURE_MOUNT_LOOP=y
+# CONFIG_FEATURE_MTAB_SUPPORT is not set
+
+#
+# Miscellaneous Utilities
+#
+CONFIG_ADJTIMEX=y
+# CONFIG_BBCONFIG is not set
+# CONFIG_CHAT is not set
+# CONFIG_FEATURE_CHAT_NOFAIL is not set
+# CONFIG_FEATURE_CHAT_TTY_HIFI is not set
+# CONFIG_FEATURE_CHAT_IMPLICIT_CR is not set
+# CONFIG_FEATURE_CHAT_SWALLOW_OPTS is not set
+# CONFIG_FEATURE_CHAT_SEND_ESCAPES is not set
+# CONFIG_FEATURE_CHAT_VAR_ABORT_LEN is not set
+# CONFIG_FEATURE_CHAT_CLR_ABORT is not set
+CONFIG_CHRT=y
+CONFIG_CROND=y
+# CONFIG_DEBUG_CROND_OPTION is not set
+CONFIG_FEATURE_CROND_CALL_SENDMAIL=y
+CONFIG_CRONTAB=y
+CONFIG_DC=y
+# CONFIG_DEVFSD is not set
+# CONFIG_DEVFSD_MODLOAD is not set
+# CONFIG_DEVFSD_FG_NP is not set
+# CONFIG_DEVFSD_VERBOSE is not set
+# CONFIG_FEATURE_DEVFS is not set
+CONFIG_EJECT=y
+CONFIG_FEATURE_EJECT_SCSI=y
+CONFIG_LAST=y
+CONFIG_LESS=y
+CONFIG_FEATURE_LESS_MAXLINES=9999999
+CONFIG_FEATURE_LESS_BRACKETS=y
+CONFIG_FEATURE_LESS_FLAGS=y
+CONFIG_FEATURE_LESS_FLAGCS=y
+CONFIG_FEATURE_LESS_MARKS=y
+CONFIG_FEATURE_LESS_REGEXP=y
+CONFIG_HDPARM=y
+CONFIG_FEATURE_HDPARM_GET_IDENTITY=y
+CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF=y
+CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF=y
+CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET=y
+CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF=y
+CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA=y
+CONFIG_MAKEDEVS=y
+# CONFIG_FEATURE_MAKEDEVS_LEAF is not set
+CONFIG_FEATURE_MAKEDEVS_TABLE=y
+CONFIG_MICROCOM=y
+CONFIG_MOUNTPOINT=y
+CONFIG_MT=y
+CONFIG_RAIDAUTORUN=y
+CONFIG_READAHEAD=y
+CONFIG_RUNLEVEL=y
+CONFIG_RX=y
+CONFIG_SCRIPT=y
+CONFIG_STRINGS=y
+CONFIG_SETSID=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 is not set
+CONFIG_ARP=y
+CONFIG_ARPING=y
+CONFIG_BRCTL=y
+CONFIG_FEATURE_BRCTL_FANCY=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_RELOAD_CONFIG_SIGHUP=y
+CONFIG_FEATURE_HTTPD_SETUID=y
+CONFIG_FEATURE_HTTPD_BASIC_AUTH=y
+CONFIG_FEATURE_HTTPD_AUTH_MD5=y
+CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES=y
+CONFIG_FEATURE_HTTPD_CGI=y
+CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR=y
+CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV=y
+CONFIG_FEATURE_HTTPD_ENCODE_URL_STR=y
+CONFIG_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 is not set
+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 is not set
+CONFIG_NC=y
+CONFIG_NC_SERVER=y
+CONFIG_NC_EXTRA=y
+CONFIG_NETSTAT=y
+CONFIG_FEATURE_NETSTAT_WIDE=y
+CONFIG_NSLOOKUP=y
+CONFIG_PING=y
+CONFIG_PING6=y
+CONFIG_FEATURE_FANCY_PING=y
+CONFIG_PSCAN=y
+CONFIG_ROUTE=y
+CONFIG_SENDMAIL=y
+CONFIG_FETCHMAIL=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_DEBUG_TFTP is not set
+CONFIG_TRACEROUTE=y
+# CONFIG_FEATURE_TRACEROUTE_VERBOSE is not set
+# CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE is not set
+# CONFIG_FEATURE_TRACEROUTE_USE_ICMP is not set
+CONFIG_APP_UDHCPD=y
+CONFIG_APP_DHCPRELAY=y
+CONFIG_APP_DUMPLEASES=y
+CONFIG_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 is not set
+# CONFIG_FEATURE_UDHCP_DEBUG is not set
+CONFIG_FEATURE_RFC3397=y
+CONFIG_DHCPC_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
+
+#
+# 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_DECIMALS is not set
+CONFIG_FEATURE_TOPMEM=y
+CONFIG_UPTIME=y
+CONFIG_WATCH=y
+
+#
+# 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_JOB_CONTROL=y
+CONFIG_ASH_READ_NCHARS=y
+CONFIG_ASH_READ_TIMEOUT=y
+# CONFIG_ASH_ALIAS is not set
+CONFIG_ASH_MATH_SUPPORT=y
+CONFIG_ASH_MATH_SUPPORT_64=y
+# CONFIG_ASH_GETOPTS is not set
+CONFIG_ASH_BUILTIN_ECHO=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 is not set
+# CONFIG_HUSH is not set
+# CONFIG_HUSH_HELP is not set
+# CONFIG_HUSH_INTERACTIVE is not set
+# CONFIG_HUSH_JOB is not set
+# CONFIG_HUSH_TICK is not set
+# CONFIG_HUSH_IF is not set
+# CONFIG_HUSH_LOOPS is not set
+# CONFIG_LASH is not set
+# CONFIG_MSH is not set
+
+#
+# Bourne Shell Options
+#
+CONFIG_FEATURE_SH_EXTRA_QUIET=y
+# CONFIG_FEATURE_SH_STANDALONE is not set
+# CONFIG_CTTYHACK is not set
+
+#
+# System Logging Utilities
+#
+CONFIG_SYSLOGD=y
+CONFIG_FEATURE_ROTATE_LOGFILE=y
+CONFIG_FEATURE_REMOTE_LOG=y
+# CONFIG_FEATURE_SYSLOGD_DUP is not set
+CONFIG_FEATURE_IPC_SYSLOG=y
+CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE=16
+CONFIG_LOGREAD=y
+CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING=y
+CONFIG_KLOGD=y
+CONFIG_LOGGER=y
+
+#
+# Runit Utilities
+#
+CONFIG_RUNSV=y
+CONFIG_RUNSVDIR=y
+CONFIG_SV=y
+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
+
+#
+# Print Utilities
+#
+CONFIG_LPD=y
+CONFIG_LPR=y
+CONFIG_LPQ=y
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/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..2a1a59f
--- /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) && 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 busybox 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..4837f61
--- /dev/null
@@ -0,0 +1,569 @@
+/*
+ * 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));
+               }
+               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 all syms
+                       if (!(sym->flags & SYMBOL_WRITE))
+                               goto next;
+*/
+                       sym->flags &= ~SYMBOL_WRITE;
+                       type = sym->type;
+                       if (type == S_TRISTATE) {
+                               sym_calc_value(modules_sym);
+                               if (modules_sym->curr.tri == no)
+                                       type = S_BOOLEAN;
+                       }
+                       switch (type) {
+                       case S_BOOLEAN:
+                       case S_TRISTATE:
+                               switch (sym_get_tristate_value(sym)) {
+                               case no:
+                                       fprintf(out, "# CONFIG_%s is not set\n", sym->name);
+                                       if (out_h) {
+                                               fprintf(out_h, "#undef CONFIG_%s\n", sym->name);
+                                               /* bbox */
+                                               fprintf(out_h, "#define ENABLE_%s 0\n", sym->name);
+                                               fprintf(out_h, "#define USE_%s(...)\n", sym->name);
+                                               fprintf(out_h, "#define SKIP_%s(...) __VA_ARGS__\n", sym->name);
+                                       }
+                                       break;
+                               case mod:
+                                       fprintf(out, "CONFIG_%s=m\n", sym->name);
+                                       if (out_h)
+                                               fprintf(out_h, "#define CONFIG_%s_MODULE 1\n", sym->name);
+                                       break;
+                               case yes:
+                                       fprintf(out, "CONFIG_%s=y\n", sym->name);
+                                       if (out_h) {
+                                               fprintf(out_h, "#define CONFIG_%s 1\n", sym->name);
+                                               /* bbox */
+                                               fprintf(out_h, "#define ENABLE_%s 1\n", sym->name);
+                                               fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name);
+                                               fprintf(out_h, "#define SKIP_%s(...)\n", sym->name);
+                                       }
+                                       break;
+                               }
+                               break;
+                       case S_STRING:
+                               // fix me
+                               str = sym_get_string_value(sym);
+                               fprintf(out, "CONFIG_%s=\"", sym->name);
+                               if (out_h)
+                                       fprintf(out_h, "#define CONFIG_%s \"", sym->name);
+                               do {
+                                       l = strcspn(str, "\"\\");
+                                       if (l) {
+                                               fwrite(str, l, 1, out);
+                                               if (out_h)
+                                                       fwrite(str, l, 1, out_h);
+                                       }
+                                       str += l;
+                                       while (*str == '\\' || *str == '"') {
+                                               fprintf(out, "\\%c", *str);
+                                               if (out_h)
+                                                       fprintf(out_h, "\\%c", *str);
+                                               str++;
+                                       }
+                               } while (*str);
+                               fputs("\"\n", out);
+                               if (out_h) {
+                                       fputs("\"\n", out_h);
+                                       /* bbox */
+                                       fprintf(out_h, "#define ENABLE_%s 1\n", sym->name);
+                                       fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name);
+                                       fprintf(out_h, "#define SKIP_%s(...)\n", sym->name);
+                               }
+                               break;
+                       case S_HEX:
+                               str = sym_get_string_value(sym);
+                               if (str[0] != '0' || (str[1] != 'x' && str[1] != 'X')) {
+                                       fprintf(out, "CONFIG_%s=%s\n", sym->name, str);
+                                       if (out_h) {
+                                               fprintf(out_h, "#define CONFIG_%s 0x%s\n", sym->name, str);
+                                               /* bbox */
+                                               fprintf(out_h, "#define ENABLE_%s 1\n", sym->name);
+                                               fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name);
+                                               fprintf(out_h, "#define SKIP_%s(...)\n", sym->name);
+                                       }
+                                       break;
+                               }
+                       case S_INT:
+                               str = sym_get_string_value(sym);
+                               fprintf(out, "CONFIG_%s=%s\n", sym->name, str);
+                               if (out_h) {
+                                       fprintf(out_h, "#define CONFIG_%s %s\n", sym->name, str);
+                                       /* bbox */
+                                       fprintf(out_h, "#define ENABLE_%s 1\n", sym->name);
+                                       fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name);
+                                       fprintf(out_h, "#define SKIP_%s(...)\n", sym->name);
+                               }
+                               break;
+                       }
+               }
+
+       next:
+               if (menu->list) {
+                       menu = menu->list;
+                       continue;
+               }
+               if (menu->next)
+                       menu = menu->next;
+               else while ((menu = menu->parent)) {
+                       if (menu->next) {
+                               menu = menu->next;
+                               break;
+                       }
+               }
+       }
+       fclose(out);
+       if (out_h) {
+               fclose(out_h);
+               rename(".tmpconfig.h", "include/autoconf.h");
+       }
+       if (!name || basename != conf_def_filename) {
+               if (!name)
+                       name = conf_def_filename;
+               sprintf(tmpname, "%s.old", name);
+               rename(name, tmpname);
+       }
+       sprintf(tmpname, "%s%s", dirname, basename);
+       if (rename(newname, tmpname))
+               return 1;
+
+       sym_change_count = 0;
+
+       return 0;
+}
diff --git a/scripts/kconfig/expr.c b/scripts/kconfig/expr.c
new file mode 100644 (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..fd3002b
--- /dev/null
@@ -0,0 +1,1645 @@
+/* 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 busybox configuration tool\n"
+           "for Linux.\n"
+           "For each option, a blank box indicates the feature is disabled, a\n"
+           "check indicates it is enabled, and a dot indicates that it is to\n"
+           "be compiled as a module.  Clicking on the box will cycle through the three states.\n"
+           "\n"
+           "If you do not see an option (e.g., a device driver) that you\n"
+           "believe should be present, try turning on Show All Options\n"
+           "under the Options menu.\n"
+           "Although there is no cross reference yet to help you figure out\n"
+           "what other options must be enabled to support the option you\n"
+           "are interested in, you can still view the help of a grayed-out\n"
+           "option.\n"
+           "\n"
+           "Toggling Show Debug Info under the Options menu will show\n"
+           "the dependencies, which you can then match by examining other options.");
+
+       dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd),
+                                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                                       GTK_MESSAGE_INFO,
+                                       GTK_BUTTONS_CLOSE, intro_text);
+       g_signal_connect_swapped(GTK_OBJECT(dialog), "response",
+                                G_CALLBACK(gtk_widget_destroy),
+                                GTK_OBJECT(dialog));
+       gtk_widget_show_all(dialog);
+}
+
+
+void on_about1_activate(GtkMenuItem * menuitem, gpointer user_data)
+{
+       GtkWidget *dialog;
+       const gchar *about_text =
+           _("gkc is copyright (c) 2002 Romain Lievin <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..7a4ca2c
--- /dev/null
@@ -0,0 +1,2317 @@
+
+#line 3 "scripts/kconfig/lex.zconf.c"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 31
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <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 (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
+
+#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..b5d3b10
--- /dev/null
@@ -0,0 +1,4 @@
+This is NOT the official version of dialog.  This version has been
+significantly modified from the original.  It is for use by the Linux
+busybox configuration script.  Please do not bother Savio Lam with
+questions about this program.
diff --git a/scripts/kconfig/lxdialog/Makefile b/scripts/kconfig/lxdialog/Makefile
new file mode 100644 (file)
index 0000000..a8b0263
--- /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_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..9e277b1
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+# Check ncurses compatibility
+
+# What library to link
+ldflags()
+{
+       $cc -print-file-name=libncursesw.so | grep -q /
+       if [ $? -eq 0 ]; then
+               echo '-lncursesw'
+               exit
+       fi
+       $cc -print-file-name=libncurses.so | grep -q /
+       if [ $? -eq 0 ]; then
+               echo '-lncurses'
+               exit
+       fi
+       $cc -print-file-name=libcurses.so | grep -q /
+       if [ $? -eq 0 ]; then
+               echo '-lcurses'
+               exit
+       fi
+       #bbox# exit 1
+       echo '-lcurses'
+       exit
+}
+
+# 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() {
+       echo "main() {}" | $cc -xc - -o $tmp 2> /dev/null
+       if [ $? != 0 ]; then
+               echo " *** Unable to find the ncurses libraries."          1>&2
+               echo " *** make menuconfig require the ncurses libraries"  1>&2
+               echo " *** "                                               1>&2
+               echo " *** Install ncurses (ncurses-devel) and try again"  1>&2
+               echo " *** "                                               1>&2
+               exit 1
+       fi
+}
+
+usage() {
+       printf "Usage: $0 [-check compiler options|-header|-library]\n"
+}
+
+if [ $# == 0 ]; then
+       usage
+       exit 1
+fi
+
+cc=""
+case "$1" in
+       "-check")
+               shift
+               cc="$@"
+               check
+               ;;
+       "-ccflags")
+               ccflags
+               ;;
+       "-ldflags")
+               shift
+               cc="$@"
+               ldflags
+               ;;
+       "*")
+               usage
+               exit 1
+               ;;
+esac
diff --git a/scripts/kconfig/lxdialog/checklist.c b/scripts/kconfig/lxdialog/checklist.c
new file mode 100644 (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..810b018
--- /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 busybox configurations.\n"
+"\n"
+"At the end of the main menu you will find two options.  One is\n"
+"for saving the current configuration to a file of your choosing.\n"
+"The other option is for loading a previously saved alternate\n"
+"configuration.\n"
+"\n"
+"Even if you don't use alternate configuration files, but you\n"
+"find during a Menuconfig session that you have completely messed\n"
+"up your settings, you may use the \"Load Alternate...\" option to\n"
+"restore your previously saved settings from \".config\" without\n"
+"restarting Menuconfig.\n"
+"\n"
+"Other information\n"
+"-----------------\n"
+"If you use Menuconfig in an XTERM window make sure you have your\n"
+"$TERM variable set to point to a xterm definition which supports color.\n"
+"Otherwise, Menuconfig will look rather bad.  Menuconfig will not\n"
+"display correctly in a RXVT window because rxvt displays only one\n"
+"intensity of color, bright.\n"
+"\n"
+"Menuconfig will display larger menus on screens or xterms which are\n"
+"set to display more than the standard 25 row by 80 column geometry.\n"
+"In order for this to work, the \"stty size\" command must be able to\n"
+"display the screen's current row and column geometry.  I STRONGLY\n"
+"RECOMMEND that you make sure you do NOT have the shell variables\n"
+"LINES and COLUMNS exported into your environment.  Some distributions\n"
+"export those variables via /etc/profile.  Some ncurses programs can\n"
+"become confused when those variables (LINES & COLUMNS) don't reflect\n"
+"the true screen size.\n"
+"\n"
+"Optional personality available\n"
+"------------------------------\n"
+"If you prefer to have all of the busybox 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 busybox\n"
+       "configurations available on a single machine.\n"
+       "\n"
+       "If you have saved a previous configuration in a file other than \n"
+       "busybox's default, entering the name of the file here will allow you\n"
+       "to modify that configuration.\n"
+       "\n"
+       "If you are uncertain, then you have probably never used alternate\n"
+       "configuration files.  You should therefor leave this blank to abort.\n"),
+save_config_text[] = N_(
+       "Enter a filename to which this configuration should be saved "
+       "as an alternate.  Leave blank to abort."),
+save_config_help[] = N_(
+       "\n"
+       "For various reasons, one may wish to keep different busybox\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 busybox configuration?"));
+               cprint("5");
+               cprint("60");
+               stat = exec_conf();
+       } while (stat < 0);
+
+       if (stat == 0) {
+               if (conf_write(NULL)) {
+                       fprintf(stderr, _("\n\n"
+                               "Error during writing of the busybox configuration.\n"
+                               "Your busybox configuration changes were NOT saved."
+                               "\n\n"));
+                       return 1;
+               }
+               printf(_("\n\n"
+                       "*** End of busybox configuration.\n"
+                       "*** Execute 'make' to build busybox or try 'make help'."
+                       "\n\n"));
+       } else {
+               fprintf(stderr, _("\n\n"
+                       "Your busybox configuration changes were NOT saved."
+                       "\n\n"));
+       }
+
+       return 0;
+}
diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c
new file mode 100644 (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..605b025
--- /dev/null
@@ -0,0 +1,1426 @@
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <qapplication.h>
+#include <qmainwindow.h>
+#include <qtoolbar.h>
+#include <qvbox.h>
+#include <qsplitter.h>
+#include <qlistview.h>
+#include <qtextview.h>
+#include <qlineedit.h>
+#include <qmenubar.h>
+#include <qmessagebox.h>
+#include <qaction.h>
+#include <qheader.h>
+#include <qfiledialog.h>
+#include <qregexp.h>
+
+#include <stdlib.h>
+
+#include "lkc.h"
+#include "qconf.h"
+
+#include "qconf.moc"
+#include "images.c"
+
+#ifdef _
+# undef _
+# define _ qgettext
+#endif
+
+static QApplication *configApp;
+
+static inline QString qgettext(const char* str)
+{
+  return QString::fromLocal8Bit(gettext(str));
+}
+
+static inline QString qgettext(const QString& str)
+{
+  return QString::fromLocal8Bit(gettext(str.latin1()));
+}
+
+ConfigSettings::ConfigSettings()
+       : showAll(false), showName(false), showRange(false), showData(false)
+{
+}
+
+#if QT_VERSION >= 300
+/**
+ * Reads the list column settings from the application settings.
+ */
+void ConfigSettings::readListSettings()
+{
+       showAll = readBoolEntry("/kconfig/qconf/showAll", false);
+       showName = readBoolEntry("/kconfig/qconf/showName", false);
+       showRange = readBoolEntry("/kconfig/qconf/showRange", false);
+       showData = readBoolEntry("/kconfig/qconf/showData", false);
+}
+
+/**
+ * Reads a list of integer values from the application settings.
+ */
+QValueList<int> ConfigSettings::readSizes(const QString& key, bool *ok)
+{
+       QValueList<int> result;
+       QStringList entryList = readListEntry(key, ok);
+       if (ok) {
+               QStringList::Iterator it;
+               for (it = entryList.begin(); it != entryList.end(); ++it)
+                       result.push_back((*it).toInt());
+       }
+
+       return result;
+}
+
+/**
+ * Writes a list of integer values to the application settings.
+ */
+bool ConfigSettings::writeSizes(const QString& key, const QValueList<int>& value)
+{
+       QStringList stringList;
+       QValueList<int>::ConstIterator it;
+
+       for (it = value.begin(); it != value.end(); ++it)
+               stringList.push_back(QString::number(*it));
+       return writeEntry(key, stringList);
+}
+#endif
+
+
+/*
+ * update all the children of a menu entry
+ *   removes/adds the entries from the parent widget as necessary
+ *
+ * parent: either the menu list widget or a menu entry widget
+ * menu: entry to be updated
+ */
+template <class P>
+void ConfigList::updateMenuList(P* parent, struct menu* menu)
+{
+       struct menu* child;
+       ConfigItem* item;
+       ConfigItem* last;
+       bool visible;
+       enum prop_type type;
+
+       if (!menu) {
+               while ((item = parent->firstChild()))
+                       delete item;
+               return;
+       }
+
+       last = parent->firstChild();
+       if (last && !last->goParent)
+               last = 0;
+       for (child = menu->list; child; child = child->next) {
+               item = last ? last->nextSibling() : parent->firstChild();
+               type = child->prompt ? child->prompt->type : P_UNKNOWN;
+
+               switch (mode) {
+               case menuMode:
+                       if (!(child->flags & MENU_ROOT))
+                               goto hide;
+                       break;
+               case symbolMode:
+                       if (child->flags & MENU_ROOT)
+                               goto hide;
+                       break;
+               default:
+                       break;
+               }
+
+               visible = menu_is_visible(child);
+               if (showAll || visible) {
+                       if (!item || item->menu != child)
+                               item = new ConfigItem(parent, last, child, visible);
+                       else
+                               item->testUpdateMenu(visible);
+
+                       if (mode == fullMode || mode == menuMode || type != P_MENU)
+                               updateMenuList(item, child);
+                       else
+                               updateMenuList(item, 0);
+                       last = item;
+                       continue;
+               }
+       hide:
+               if (item && item->menu == child) {
+                       last = parent->firstChild();
+                       if (last == item)
+                               last = 0;
+                       else while (last->nextSibling() != item)
+                               last = last->nextSibling();
+                       delete item;
+               }
+       }
+}
+
+#if QT_VERSION >= 300
+/*
+ * set the new data
+ * TODO check the value
+ */
+void ConfigItem::okRename(int col)
+{
+       Parent::okRename(col);
+       sym_set_string_value(menu->sym, text(dataColIdx).latin1());
+}
+#endif
+
+/*
+ * update the displayed of a menu entry
+ */
+void ConfigItem::updateMenu(void)
+{
+       ConfigList* list;
+       struct symbol* sym;
+       struct property *prop;
+       QString prompt;
+       int type;
+       tristate expr;
+
+       list = listView();
+       if (goParent) {
+               setPixmap(promptColIdx, list->menuBackPix);
+               prompt = "..";
+               goto set_prompt;
+       }
+
+       sym = menu->sym;
+       prop = menu->prompt;
+       prompt = QString::fromLocal8Bit(menu_get_prompt(menu));
+
+       if (prop) switch (prop->type) {
+       case P_MENU:
+               if (list->mode == singleMode || list->mode == symbolMode) {
+                       /* a menuconfig entry is displayed differently
+                        * depending whether it's at the view root or a child.
+                        */
+                       if (sym && list->rootEntry == menu)
+                               break;
+                       setPixmap(promptColIdx, list->menuPix);
+               } else {
+                       if (sym)
+                               break;
+                       setPixmap(promptColIdx, 0);
+               }
+               goto set_prompt;
+       case P_COMMENT:
+               setPixmap(promptColIdx, 0);
+               goto set_prompt;
+       default:
+               ;
+       }
+       if (!sym)
+               goto set_prompt;
+
+       setText(nameColIdx, QString::fromLocal8Bit(sym->name));
+
+       type = sym_get_type(sym);
+       switch (type) {
+       case S_BOOLEAN:
+       case S_TRISTATE:
+               char ch;
+
+               if (!sym_is_changable(sym) && !list->showAll) {
+                       setPixmap(promptColIdx, 0);
+                       setText(noColIdx, QString::null);
+                       setText(modColIdx, QString::null);
+                       setText(yesColIdx, QString::null);
+                       break;
+               }
+               expr = sym_get_tristate_value(sym);
+               switch (expr) {
+               case yes:
+                       if (sym_is_choice_value(sym) && type == S_BOOLEAN)
+                               setPixmap(promptColIdx, list->choiceYesPix);
+                       else
+                               setPixmap(promptColIdx, list->symbolYesPix);
+                       setText(yesColIdx, "Y");
+                       ch = 'Y';
+                       break;
+               case mod:
+                       setPixmap(promptColIdx, list->symbolModPix);
+                       setText(modColIdx, "M");
+                       ch = 'M';
+                       break;
+               default:
+                       if (sym_is_choice_value(sym) && type == S_BOOLEAN)
+                               setPixmap(promptColIdx, list->choiceNoPix);
+                       else
+                               setPixmap(promptColIdx, list->symbolNoPix);
+                       setText(noColIdx, "N");
+                       ch = 'N';
+                       break;
+               }
+               if (expr != no)
+                       setText(noColIdx, sym_tristate_within_range(sym, no) ? "_" : 0);
+               if (expr != mod)
+                       setText(modColIdx, sym_tristate_within_range(sym, mod) ? "_" : 0);
+               if (expr != yes)
+                       setText(yesColIdx, sym_tristate_within_range(sym, yes) ? "_" : 0);
+
+               setText(dataColIdx, QChar(ch));
+               break;
+       case S_INT:
+       case S_HEX:
+       case S_STRING:
+               const char* data;
+
+               data = sym_get_string_value(sym);
+
+#if QT_VERSION >= 300
+               int i = list->mapIdx(dataColIdx);
+               if (i >= 0)
+                       setRenameEnabled(i, TRUE);
+#endif
+               setText(dataColIdx, data);
+               if (type == S_STRING)
+                       prompt = QString("%1: %2").arg(prompt).arg(data);
+               else
+                       prompt = QString("(%2) %1").arg(prompt).arg(data);
+               break;
+       }
+       if (!sym_has_value(sym) && visible)
+               prompt += " (NEW)";
+set_prompt:
+       setText(promptColIdx, prompt);
+}
+
+void ConfigItem::testUpdateMenu(bool v)
+{
+       ConfigItem* i;
+
+       visible = v;
+       if (!menu)
+               return;
+
+       sym_calc_value(menu->sym);
+       if (menu->flags & MENU_CHANGED) {
+               /* the menu entry changed, so update all list items */
+               menu->flags &= ~MENU_CHANGED;
+               for (i = (ConfigItem*)menu->data; i; i = i->nextItem)
+                       i->updateMenu();
+       } else if (listView()->updateAll)
+               updateMenu();
+}
+
+void ConfigItem::paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align)
+{
+       ConfigList* list = listView();
+
+       if (visible) {
+               if (isSelected() && !list->hasFocus() && list->mode == menuMode)
+                       Parent::paintCell(p, list->inactivedColorGroup, column, width, align);
+               else
+                       Parent::paintCell(p, cg, column, width, align);
+       } else
+               Parent::paintCell(p, list->disabledColorGroup, column, width, align);
+}
+
+/*
+ * construct a menu entry
+ */
+void ConfigItem::init(void)
+{
+       if (menu) {
+               ConfigList* list = listView();
+               nextItem = (ConfigItem*)menu->data;
+               menu->data = this;
+
+               if (list->mode != fullMode)
+                       setOpen(TRUE);
+               sym_calc_value(menu->sym);
+       }
+       updateMenu();
+}
+
+/*
+ * destruct a menu entry
+ */
+ConfigItem::~ConfigItem(void)
+{
+       if (menu) {
+               ConfigItem** ip = (ConfigItem**)&menu->data;
+               for (; *ip; ip = &(*ip)->nextItem) {
+                       if (*ip == this) {
+                               *ip = nextItem;
+                               break;
+                       }
+               }
+       }
+}
+
+void ConfigLineEdit::show(ConfigItem* i)
+{
+       item = i;
+       if (sym_get_string_value(item->menu->sym))
+               setText(QString::fromLocal8Bit(sym_get_string_value(item->menu->sym)));
+       else
+               setText(QString::null);
+       Parent::show();
+       setFocus();
+}
+
+void ConfigLineEdit::keyPressEvent(QKeyEvent* e)
+{
+       switch (e->key()) {
+       case Key_Escape:
+               break;
+       case Key_Return:
+       case Key_Enter:
+               sym_set_string_value(item->menu->sym, text().latin1());
+               parent()->updateList(item);
+               break;
+       default:
+               Parent::keyPressEvent(e);
+               return;
+       }
+       e->accept();
+       parent()->list->setFocus();
+       hide();
+}
+
+ConfigList::ConfigList(ConfigView* p, ConfigMainWindow* cv, ConfigSettings* configSettings)
+       : Parent(p), cview(cv),
+         updateAll(false),
+         symbolYesPix(xpm_symbol_yes), symbolModPix(xpm_symbol_mod), symbolNoPix(xpm_symbol_no),
+         choiceYesPix(xpm_choice_yes), choiceNoPix(xpm_choice_no),
+         menuPix(xpm_menu), menuInvPix(xpm_menu_inv), menuBackPix(xpm_menuback), voidPix(xpm_void),
+         showAll(false), showName(false), showRange(false), showData(false),
+         rootEntry(0)
+{
+       int i;
+
+       setSorting(-1);
+       setRootIsDecorated(TRUE);
+       disabledColorGroup = palette().active();
+       disabledColorGroup.setColor(QColorGroup::Text, palette().disabled().text());
+       inactivedColorGroup = palette().active();
+       inactivedColorGroup.setColor(QColorGroup::Highlight, palette().disabled().highlight());
+
+       connect(this, SIGNAL(selectionChanged(void)),
+               SLOT(updateSelection(void)));
+
+       if (configSettings) {
+               showAll = configSettings->showAll;
+               showName = configSettings->showName;
+               showRange = configSettings->showRange;
+               showData = configSettings->showData;
+       }
+
+       for (i = 0; i < colNr; i++)
+               colMap[i] = colRevMap[i] = -1;
+       addColumn(promptColIdx, "Option");
+
+       reinit();
+}
+
+void ConfigList::reinit(void)
+{
+       removeColumn(dataColIdx);
+       removeColumn(yesColIdx);
+       removeColumn(modColIdx);
+       removeColumn(noColIdx);
+       removeColumn(nameColIdx);
+
+       if (showName)
+               addColumn(nameColIdx, "Name");
+       if (showRange) {
+               addColumn(noColIdx, "N");
+               addColumn(modColIdx, "M");
+               addColumn(yesColIdx, "Y");
+       }
+       if (showData)
+               addColumn(dataColIdx, "Value");
+
+       updateListAll();
+}
+
+void ConfigList::updateSelection(void)
+{
+       struct menu *menu;
+       enum prop_type type;
+
+       ConfigItem* item = (ConfigItem*)selectedItem();
+       if (!item)
+               return;
+
+       cview->setHelp(item);
+
+       menu = item->menu;
+       if (!menu)
+               return;
+       type = menu->prompt ? menu->prompt->type : P_UNKNOWN;
+       if (mode == menuMode && type == P_MENU)
+               emit menuSelected(menu);
+}
+
+void ConfigList::updateList(ConfigItem* item)
+{
+       ConfigItem* last = 0;
+
+       if (!rootEntry)
+               goto update;
+
+       if (rootEntry != &rootmenu && (mode == singleMode ||
+           (mode == symbolMode && rootEntry->parent != &rootmenu))) {
+               item = firstChild();
+               if (!item)
+                       item = new ConfigItem(this, 0, true);
+               last = item;
+       }
+       if ((mode == singleMode || (mode == symbolMode && !(rootEntry->flags & MENU_ROOT))) &&
+           rootEntry->sym && rootEntry->prompt) {
+               item = last ? last->nextSibling() : firstChild();
+               if (!item)
+                       item = new ConfigItem(this, last, rootEntry, true);
+               else
+                       item->testUpdateMenu(true);
+
+               updateMenuList(item, rootEntry);
+               triggerUpdate();
+               return;
+       }
+update:
+       updateMenuList(this, rootEntry);
+       triggerUpdate();
+}
+
+void ConfigList::setAllOpen(bool open)
+{
+       QListViewItemIterator it(this);
+
+       for (; it.current(); it++)
+               it.current()->setOpen(open);
+}
+
+void ConfigList::setValue(ConfigItem* item, tristate val)
+{
+       struct symbol* sym;
+       int type;
+       tristate oldval;
+
+       sym = item->menu ? item->menu->sym : 0;
+       if (!sym)
+               return;
+
+       type = sym_get_type(sym);
+       switch (type) {
+       case S_BOOLEAN:
+       case S_TRISTATE:
+               oldval = sym_get_tristate_value(sym);
+
+               if (!sym_set_tristate_value(sym, val))
+                       return;
+               if (oldval == no && item->menu->list)
+                       item->setOpen(TRUE);
+               parent()->updateList(item);
+               break;
+       }
+}
+
+void ConfigList::changeValue(ConfigItem* item)
+{
+       struct symbol* sym;
+       struct menu* menu;
+       int type, oldexpr, newexpr;
+
+       menu = item->menu;
+       if (!menu)
+               return;
+       sym = menu->sym;
+       if (!sym) {
+               if (item->menu->list)
+                       item->setOpen(!item->isOpen());
+               return;
+       }
+
+       type = sym_get_type(sym);
+       switch (type) {
+       case S_BOOLEAN:
+       case S_TRISTATE:
+               oldexpr = sym_get_tristate_value(sym);
+               newexpr = sym_toggle_tristate_value(sym);
+               if (item->menu->list) {
+                       if (oldexpr == newexpr)
+                               item->setOpen(!item->isOpen());
+                       else if (oldexpr == no)
+                               item->setOpen(TRUE);
+               }
+               if (oldexpr != newexpr)
+                       parent()->updateList(item);
+               break;
+       case S_INT:
+       case S_HEX:
+       case S_STRING:
+#if QT_VERSION >= 300
+               if (colMap[dataColIdx] >= 0)
+                       item->startRename(colMap[dataColIdx]);
+               else
+#endif
+                       parent()->lineEdit->show(item);
+               break;
+       }
+}
+
+void ConfigList::setRootMenu(struct menu *menu)
+{
+       enum prop_type type;
+
+       if (rootEntry == menu)
+               return;
+       type = menu && menu->prompt ? menu->prompt->type : P_UNKNOWN;
+       if (type != P_MENU)
+               return;
+       updateMenuList(this, 0);
+       rootEntry = menu;
+       updateListAll();
+       setSelected(currentItem(), hasFocus());
+}
+
+void ConfigList::setParentMenu(void)
+{
+       ConfigItem* item;
+       struct menu *oldroot;
+
+       oldroot = rootEntry;
+       if (rootEntry == &rootmenu)
+               return;
+       setRootMenu(menu_get_parent_menu(rootEntry->parent));
+
+       QListViewItemIterator it(this);
+       for (; (item = (ConfigItem*)it.current()); it++) {
+               if (item->menu == oldroot) {
+                       setCurrentItem(item);
+                       ensureItemVisible(item);
+                       break;
+               }
+       }
+}
+
+void ConfigList::keyPressEvent(QKeyEvent* ev)
+{
+       QListViewItem* i = currentItem();
+       ConfigItem* item;
+       struct menu *menu;
+       enum prop_type type;
+
+       if (ev->key() == Key_Escape && mode != fullMode) {
+               emit parentSelected();
+               ev->accept();
+               return;
+       }
+
+       if (!i) {
+               Parent::keyPressEvent(ev);
+               return;
+       }
+       item = (ConfigItem*)i;
+
+       switch (ev->key()) {
+       case Key_Return:
+       case Key_Enter:
+               if (item->goParent) {
+                       emit parentSelected();
+                       break;
+               }
+               menu = item->menu;
+               if (!menu)
+                       break;
+               type = menu->prompt ? menu->prompt->type : P_UNKNOWN;
+               if (type == P_MENU && rootEntry != menu &&
+                   mode != fullMode && mode != menuMode) {
+                       emit menuSelected(menu);
+                       break;
+               }
+       case Key_Space:
+               changeValue(item);
+               break;
+       case Key_N:
+               setValue(item, no);
+               break;
+       case Key_M:
+               setValue(item, mod);
+               break;
+       case Key_Y:
+               setValue(item, yes);
+               break;
+       default:
+               Parent::keyPressEvent(ev);
+               return;
+       }
+       ev->accept();
+}
+
+void ConfigList::contentsMousePressEvent(QMouseEvent* e)
+{
+       //QPoint p(contentsToViewport(e->pos()));
+       //printf("contentsMousePressEvent: %d,%d\n", p.x(), p.y());
+       Parent::contentsMousePressEvent(e);
+}
+
+void ConfigList::contentsMouseReleaseEvent(QMouseEvent* e)
+{
+       QPoint p(contentsToViewport(e->pos()));
+       ConfigItem* item = (ConfigItem*)itemAt(p);
+       struct menu *menu;
+       enum prop_type ptype;
+       const QPixmap* pm;
+       int idx, x;
+
+       if (!item)
+               goto skip;
+
+       menu = item->menu;
+       x = header()->offset() + p.x();
+       idx = colRevMap[header()->sectionAt(x)];
+       switch (idx) {
+       case promptColIdx:
+               pm = item->pixmap(promptColIdx);
+               if (pm) {
+                       int off = header()->sectionPos(0) + itemMargin() +
+                               treeStepSize() * (item->depth() + (rootIsDecorated() ? 1 : 0));
+                       if (x >= off && x < off + pm->width()) {
+                               if (item->goParent) {
+                                       emit parentSelected();
+                                       break;
+                               } else if (!menu)
+                                       break;
+                               ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN;
+                               if (ptype == P_MENU && rootEntry != menu &&
+                                   mode != fullMode && mode != menuMode)
+                                       emit menuSelected(menu);
+                               else
+                                       changeValue(item);
+                       }
+               }
+               break;
+       case noColIdx:
+               setValue(item, no);
+               break;
+       case modColIdx:
+               setValue(item, mod);
+               break;
+       case yesColIdx:
+               setValue(item, yes);
+               break;
+       case dataColIdx:
+               changeValue(item);
+               break;
+       }
+
+skip:
+       //printf("contentsMouseReleaseEvent: %d,%d\n", p.x(), p.y());
+       Parent::contentsMouseReleaseEvent(e);
+}
+
+void ConfigList::contentsMouseMoveEvent(QMouseEvent* e)
+{
+       //QPoint p(contentsToViewport(e->pos()));
+       //printf("contentsMouseMoveEvent: %d,%d\n", p.x(), p.y());
+       Parent::contentsMouseMoveEvent(e);
+}
+
+void ConfigList::contentsMouseDoubleClickEvent(QMouseEvent* e)
+{
+       QPoint p(contentsToViewport(e->pos()));
+       ConfigItem* item = (ConfigItem*)itemAt(p);
+       struct menu *menu;
+       enum prop_type ptype;
+
+       if (!item)
+               goto skip;
+       if (item->goParent) {
+               emit parentSelected();
+               goto skip;
+       }
+       menu = item->menu;
+       if (!menu)
+               goto skip;
+       ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN;
+       if (ptype == P_MENU && (mode == singleMode || mode == symbolMode))
+               emit menuSelected(menu);
+       else if (menu->sym)
+               changeValue(item);
+
+skip:
+       //printf("contentsMouseDoubleClickEvent: %d,%d\n", p.x(), p.y());
+       Parent::contentsMouseDoubleClickEvent(e);
+}
+
+void ConfigList::focusInEvent(QFocusEvent *e)
+{
+       Parent::focusInEvent(e);
+
+       QListViewItem* item = currentItem();
+       if (!item)
+               return;
+
+       setSelected(item, TRUE);
+       emit gotFocus();
+}
+
+ConfigView* ConfigView::viewList;
+
+ConfigView::ConfigView(QWidget* parent, ConfigMainWindow* cview,
+                      ConfigSettings *configSettings)
+       : Parent(parent)
+{
+       list = new ConfigList(this, cview, configSettings);
+       lineEdit = new ConfigLineEdit(this);
+       lineEdit->hide();
+
+       this->nextView = viewList;
+       viewList = this;
+}
+
+ConfigView::~ConfigView(void)
+{
+       ConfigView** vp;
+
+       for (vp = &viewList; *vp; vp = &(*vp)->nextView) {
+               if (*vp == this) {
+                       *vp = nextView;
+                       break;
+               }
+       }
+}
+
+void ConfigView::updateList(ConfigItem* item)
+{
+       ConfigView* v;
+
+       for (v = viewList; v; v = v->nextView)
+               v->list->updateList(item);
+}
+
+void ConfigView::updateListAll(void)
+{
+       ConfigView* v;
+
+       for (v = viewList; v; v = v->nextView)
+               v->list->updateListAll();
+}
+
+/*
+ * Construct the complete config widget
+ */
+ConfigMainWindow::ConfigMainWindow(void)
+{
+       QMenuBar* menu;
+       bool ok;
+       int x, y, width, height;
+
+       QWidget *d = configApp->desktop();
+
+       ConfigSettings* configSettings = new ConfigSettings();
+#if QT_VERSION >= 300
+       width = configSettings->readNumEntry("/kconfig/qconf/window width", d->width() - 64);
+       height = configSettings->readNumEntry("/kconfig/qconf/window height", d->height() - 64);
+       resize(width, height);
+       x = configSettings->readNumEntry("/kconfig/qconf/window x", 0, &ok);
+       if (ok)
+               y = configSettings->readNumEntry("/kconfig/qconf/window y", 0, &ok);
+       if (ok)
+               move(x, y);
+       showDebug = configSettings->readBoolEntry("/kconfig/qconf/showDebug", false);
+
+       // read list settings into configSettings, will be used later for ConfigList setup
+       configSettings->readListSettings();
+#else
+       width = d->width() - 64;
+       height = d->height() - 64;
+       resize(width, height);
+       showDebug = false;
+#endif
+
+       split1 = new QSplitter(this);
+       split1->setOrientation(QSplitter::Horizontal);
+       setCentralWidget(split1);
+
+       menuView = new ConfigView(split1, this, configSettings);
+       menuList = menuView->list;
+
+       split2 = new QSplitter(split1);
+       split2->setOrientation(QSplitter::Vertical);
+
+       // create config tree
+       configView = new ConfigView(split2, this, configSettings);
+       configList = configView->list;
+
+       helpText = new QTextView(split2);
+       helpText->setTextFormat(Qt::RichText);
+
+       setTabOrder(configList, helpText);
+       configList->setFocus();
+
+       menu = menuBar();
+       toolBar = new QToolBar("Tools", this);
+
+       backAction = new QAction("Back", QPixmap(xpm_back), "Back", 0, this);
+         connect(backAction, SIGNAL(activated()), SLOT(goBack()));
+         backAction->setEnabled(FALSE);
+       QAction *quitAction = new QAction("Quit", "&Quit", CTRL+Key_Q, this);
+         connect(quitAction, SIGNAL(activated()), SLOT(close()));
+       QAction *loadAction = new QAction("Load", QPixmap(xpm_load), "&Load", CTRL+Key_L, this);
+         connect(loadAction, SIGNAL(activated()), SLOT(loadConfig()));
+       QAction *saveAction = new QAction("Save", QPixmap(xpm_save), "&Save", CTRL+Key_S, this);
+         connect(saveAction, SIGNAL(activated()), SLOT(saveConfig()));
+       QAction *saveAsAction = new QAction("Save As...", "Save &As...", 0, this);
+         connect(saveAsAction, SIGNAL(activated()), SLOT(saveConfigAs()));
+       QAction *singleViewAction = new QAction("Single View", QPixmap(xpm_single_view), "Split View", 0, this);
+         connect(singleViewAction, SIGNAL(activated()), SLOT(showSingleView()));
+       QAction *splitViewAction = new QAction("Split View", QPixmap(xpm_split_view), "Split View", 0, this);
+         connect(splitViewAction, SIGNAL(activated()), SLOT(showSplitView()));
+       QAction *fullViewAction = new QAction("Full View", QPixmap(xpm_tree_view), "Full View", 0, this);
+         connect(fullViewAction, SIGNAL(activated()), SLOT(showFullView()));
+
+       QAction *showNameAction = new QAction(NULL, "Show Name", 0, this);
+         showNameAction->setToggleAction(TRUE);
+         showNameAction->setOn(configList->showName);
+         connect(showNameAction, SIGNAL(toggled(bool)), SLOT(setShowName(bool)));
+       QAction *showRangeAction = new QAction(NULL, "Show Range", 0, this);
+         showRangeAction->setToggleAction(TRUE);
+         showRangeAction->setOn(configList->showRange);
+         connect(showRangeAction, SIGNAL(toggled(bool)), SLOT(setShowRange(bool)));
+       QAction *showDataAction = new QAction(NULL, "Show Data", 0, this);
+         showDataAction->setToggleAction(TRUE);
+         showDataAction->setOn(configList->showData);
+         connect(showDataAction, SIGNAL(toggled(bool)), SLOT(setShowData(bool)));
+       QAction *showAllAction = new QAction(NULL, "Show All Options", 0, this);
+         showAllAction->setToggleAction(TRUE);
+         showAllAction->setOn(configList->showAll);
+         connect(showAllAction, SIGNAL(toggled(bool)), SLOT(setShowAll(bool)));
+       QAction *showDebugAction = new QAction(NULL, "Show Debug Info", 0, this);
+         showDebugAction->setToggleAction(TRUE);
+         showDebugAction->setOn(showDebug);
+         connect(showDebugAction, SIGNAL(toggled(bool)), SLOT(setShowDebug(bool)));
+
+       QAction *showIntroAction = new QAction(NULL, "Introduction", 0, this);
+         connect(showIntroAction, SIGNAL(activated()), SLOT(showIntro()));
+       QAction *showAboutAction = new QAction(NULL, "About", 0, this);
+         connect(showAboutAction, SIGNAL(activated()), SLOT(showAbout()));
+
+       // init tool bar
+       backAction->addTo(toolBar);
+       toolBar->addSeparator();
+       loadAction->addTo(toolBar);
+       saveAction->addTo(toolBar);
+       toolBar->addSeparator();
+       singleViewAction->addTo(toolBar);
+       splitViewAction->addTo(toolBar);
+       fullViewAction->addTo(toolBar);
+
+       // create config menu
+       QPopupMenu* config = new QPopupMenu(this);
+       menu->insertItem("&File", config);
+       loadAction->addTo(config);
+       saveAction->addTo(config);
+       saveAsAction->addTo(config);
+       config->insertSeparator();
+       quitAction->addTo(config);
+
+       // create options menu
+       QPopupMenu* optionMenu = new QPopupMenu(this);
+       menu->insertItem("&Option", optionMenu);
+       showNameAction->addTo(optionMenu);
+       showRangeAction->addTo(optionMenu);
+       showDataAction->addTo(optionMenu);
+       optionMenu->insertSeparator();
+       showAllAction->addTo(optionMenu);
+       showDebugAction->addTo(optionMenu);
+
+       // create help menu
+       QPopupMenu* helpMenu = new QPopupMenu(this);
+       menu->insertSeparator();
+       menu->insertItem("&Help", helpMenu);
+       showIntroAction->addTo(helpMenu);
+       showAboutAction->addTo(helpMenu);
+
+       connect(configList, SIGNAL(menuSelected(struct menu *)),
+               SLOT(changeMenu(struct menu *)));
+       connect(configList, SIGNAL(parentSelected()),
+               SLOT(goBack()));
+       connect(menuList, SIGNAL(menuSelected(struct menu *)),
+               SLOT(changeMenu(struct menu *)));
+
+       connect(configList, SIGNAL(gotFocus(void)),
+               SLOT(listFocusChanged(void)));
+       connect(menuList, SIGNAL(gotFocus(void)),
+               SLOT(listFocusChanged(void)));
+
+#if QT_VERSION >= 300
+       QString listMode = configSettings->readEntry("/kconfig/qconf/listMode", "symbol");
+       if (listMode == "single")
+               showSingleView();
+       else if (listMode == "full")
+               showFullView();
+       else /*if (listMode == "split")*/
+               showSplitView();
+
+       // UI setup done, restore splitter positions
+       QValueList<int> sizes = configSettings->readSizes("/kconfig/qconf/split1", &ok);
+       if (ok)
+               split1->setSizes(sizes);
+
+       sizes = configSettings->readSizes("/kconfig/qconf/split2", &ok);
+       if (ok)
+               split2->setSizes(sizes);
+#else
+       showSplitView();
+#endif
+       delete configSettings;
+}
+
+static QString print_filter(const QString &str)
+{
+       QRegExp re("[<>&\"\\n]");
+       QString res = str;
+       for (int i = 0; (i = res.find(re, i)) >= 0;) {
+               switch (res[i].latin1()) {
+               case '<':
+                       res.replace(i, 1, "&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 busybox configuration tool for Linux.\n\n"
+               "For each option, a blank box indicates the feature is disabled, a check\n"
+               "indicates it is enabled, and a dot indicates that it is to be compiled\n"
+               "as a module.  Clicking on the box will cycle through the three states.\n\n"
+               "If you do not see an option (e.g., a device driver) that you believe\n"
+               "should be present, try turning on Show All Options under the Options menu.\n"
+               "Although there is no cross reference yet to help you figure out what other\n"
+               "options must be enabled to support the option you are interested in, you can\n"
+               "still view the help of a grayed-out option.\n\n"
+               "Toggling Show Debug Info under the Options menu will show the dependencies,\n"
+               "which you can then match by examining other options.\n\n";
+
+       QMessageBox::information(this, "qconf", str);
+}
+
+void ConfigMainWindow::showAbout(void)
+{
+       static char str[] = "qconf is Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>.\n\n"
+               "Bug reports and feature request can also be entered at http://bugs.busybox.net/\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..aea8d56
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2002-2005 Roman Zippel <zippel@linux-m68k.org>
+ * Copyright (C) 2002-2005 Sam Ravnborg <sam@ravnborg.org>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <string.h>
+#include "lkc.h"
+
+/* file already present in list? If not add it */
+struct file *file_lookup(const char *name)
+{
+       struct file *file;
+
+       for (file = file_list; file; file = file->next) {
+               if (!strcmp(name, file->name))
+                       return file;
+       }
+
+       file = malloc(sizeof(*file));
+       memset(file, 0, sizeof(*file));
+       file->name = strdup(name);
+       file->next = file_list;
+       file_list = file;
+       return file;
+}
+
+/* write a dependency file as used by kbuild to track dependencies */
+int file_write_dep(const char *name)
+{
+       struct file *file;
+       FILE *out;
+
+       if (!name)
+               name = ".kconfig.d";
+       out = fopen("..config.tmp", "w");
+       if (!out)
+               return 1;
+       fprintf(out, "deps_config := \\\n");
+       for (file = file_list; file; file = file->next) {
+               if (file->next)
+                       fprintf(out, "\t%s \\\n", file->name);
+               else
+                       fprintf(out, "\t%s\n", file->name);
+       }
+       fprintf(out, "\n.config include/autoconf.h: $(deps_config)\n\n$(deps_config):\n");
+       fclose(out);
+       rename("..config.tmp", name);
+       return 0;
+}
+
+
+/* Allocate initial growable sting */
+struct gstr str_new(void)
+{
+       struct gstr gs;
+       gs.s = malloc(sizeof(char) * 64);
+       gs.len = 16;
+       strcpy(gs.s, "\0");
+       return gs;
+}
+
+/* Allocate and assign growable string */
+struct gstr str_assign(const char *s)
+{
+       struct gstr gs;
+       gs.s = strdup(s);
+       gs.len = strlen(s) + 1;
+       return gs;
+}
+
+/* Free storage for growable string */
+void str_free(struct gstr *gs)
+{
+       if (gs->s)
+               free(gs->s);
+       gs->s = NULL;
+       gs->len = 0;
+}
+
+/* Append to growable string */
+void str_append(struct gstr *gs, const char *s)
+{
+       size_t l = strlen(gs->s) + strlen(s) + 1;
+       if (l > gs->len) {
+               gs->s   = realloc(gs->s, l);
+               gs->len = l;
+       }
+       strcat(gs->s, s);
+}
+
+/* Append printf formatted string to growable string */
+void str_printf(struct gstr *gs, const char *fmt, ...)
+{
+       va_list ap;
+       char s[10000]; /* big enough... */
+       va_start(ap, fmt);
+       vsnprintf(s, sizeof(s), fmt, ap);
+       str_append(gs, s);
+       va_end(ap);
+}
+
+/* Retrieve value of growable string */
+const char *str_get(struct gstr *gs)
+{
+       return gs->s;
+}
+
diff --git a/scripts/kconfig/zconf.gperf b/scripts/kconfig/zconf.gperf
new file mode 100644 (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..cfa4607
--- /dev/null
@@ -0,0 +1,350 @@
+%option backup nostdinit noyywrap never-interactive full ecs
+%option 8bit backup nodefault perf-report perf-report
+%x COMMAND HELP STRING PARAM
+%{
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define LKC_DIRECT_LINK
+#include "lkc.h"
+
+#define START_STRSIZE  16
+
+static struct {
+       struct file *file;
+       int lineno;
+} current_pos;
+
+static char *text;
+static int text_size, text_asize;
+
+struct buffer {
+        struct buffer *parent;
+        YY_BUFFER_STATE state;
+};
+
+struct buffer *current_buf;
+
+static int last_ts, first_ts;
+
+static void zconf_endhelp(void);
+static void zconf_endfile(void);
+
+void new_string(void)
+{
+       text = malloc(START_STRSIZE);
+       text_asize = START_STRSIZE;
+       text_size = 0;
+       *text = 0;
+}
+
+void append_string(const char *str, int size)
+{
+       int new_size = text_size + size + 1;
+       if (new_size > text_asize) {
+               new_size += START_STRSIZE - 1;
+               new_size &= -START_STRSIZE;
+               text = realloc(text, new_size);
+               text_asize = new_size;
+       }
+       memcpy(text + text_size, str, size);
+       text_size += size;
+       text[text_size] = 0;
+}
+
+void alloc_string(const char *str, int size)
+{
+       text = malloc(size + 1);
+       memcpy(text, str, size);
+       text[size] = 0;
+}
+%}
+
+ws     [ \n\t]
+n      [A-Za-z0-9_]
+
+%%
+       int str = 0;
+       int ts, i;
+
+[ \t]*#.*\n    |
+[ \t]*\n       {
+       current_file->lineno++;
+       return T_EOL;
+}
+[ \t]*#.*
+
+
+[ \t]+ {
+       BEGIN(COMMAND);
+}
+
+.      {
+       unput(yytext[0]);
+       BEGIN(COMMAND);
+}
+
+
+<COMMAND>{
+       {n}+    {
+               struct kconf_id *id = kconf_id_lookup(yytext, yyleng);
+               BEGIN(PARAM);
+               current_pos.file = current_file;
+               current_pos.lineno = current_file->lineno;
+               if (id && id->flags & TF_COMMAND) {
+                       zconflval.id = id;
+                       return id->token;
+               }
+               alloc_string(yytext, yyleng);
+               zconflval.string = text;
+               return T_WORD;
+       }
+       .
+       \n      {
+               BEGIN(INITIAL);
+               current_file->lineno++;
+               return T_EOL;
+       }
+}
+
+<PARAM>{
+       "&&"    return T_AND;
+       "||"    return T_OR;
+       "("     return T_OPEN_PAREN;
+       ")"     return T_CLOSE_PAREN;
+       "!"     return T_NOT;
+       "="     return T_EQUAL;
+       "!="    return T_UNEQUAL;
+       \"|\'   {
+               str = yytext[0];
+               new_string();
+               BEGIN(STRING);
+       }
+       \n      BEGIN(INITIAL); current_file->lineno++; return T_EOL;
+       ---     /* ignore */
+       ({n}|[-/.])+    {
+               struct kconf_id *id = kconf_id_lookup(yytext, yyleng);
+               if (id && id->flags & TF_PARAM) {
+                       zconflval.id = id;
+                       return id->token;
+               }
+               alloc_string(yytext, yyleng);
+               zconflval.string = text;
+               return T_WORD;
+       }
+       #.*     /* comment */
+       \\\n    current_file->lineno++;
+       .
+       <<EOF>> {
+               BEGIN(INITIAL);
+       }
+}
+
+<STRING>{
+       [^'"\\\n]+/\n   {
+               append_string(yytext, yyleng);
+               zconflval.string = text;
+               return T_WORD_QUOTE;
+       }
+       [^'"\\\n]+      {
+               append_string(yytext, yyleng);
+       }
+       \\.?/\n {
+               append_string(yytext + 1, yyleng - 1);
+               zconflval.string = text;
+               return T_WORD_QUOTE;
+       }
+       \\.?    {
+               append_string(yytext + 1, yyleng - 1);
+       }
+       \'|\"   {
+               if (str == yytext[0]) {
+                       BEGIN(PARAM);
+                       zconflval.string = text;
+                       return T_WORD_QUOTE;
+               } else
+                       append_string(yytext, 1);
+       }
+       \n      {
+               printf("%s:%d:warning: multi-line strings not supported\n", zconf_curname(), zconf_lineno());
+               current_file->lineno++;
+               BEGIN(INITIAL);
+               return T_EOL;
+       }
+       <<EOF>> {
+               BEGIN(INITIAL);
+       }
+}
+
+<HELP>{
+       [ \t]+  {
+               ts = 0;
+               for (i = 0; i < yyleng; i++) {
+                       if (yytext[i] == '\t')
+                               ts = (ts & ~7) + 8;
+                       else
+                               ts++;
+               }
+               last_ts = ts;
+               if (first_ts) {
+                       if (ts < first_ts) {
+                               zconf_endhelp();
+                               return T_HELPTEXT;
+                       }
+                       ts -= first_ts;
+                       while (ts > 8) {
+                               append_string("        ", 8);
+                               ts -= 8;
+                       }
+                       append_string("        ", ts);
+               }
+       }
+       [ \t]*\n/[^ \t\n] {
+               current_file->lineno++;
+               zconf_endhelp();
+               return T_HELPTEXT;
+       }
+       [ \t]*\n        {
+               current_file->lineno++;
+               append_string("\n", 1);
+       }
+       [^ \t\n].* {
+               append_string(yytext, yyleng);
+               if (!first_ts)
+                       first_ts = last_ts;
+       }
+       <<EOF>> {
+               zconf_endhelp();
+               return T_HELPTEXT;
+       }
+}
+
+<<EOF>>        {
+       if (current_file) {
+               zconf_endfile();
+               return T_EOL;
+       }
+       fclose(yyin);
+       yyterminate();
+}
+
+%%
+void zconf_starthelp(void)
+{
+       new_string();
+       last_ts = first_ts = 0;
+       BEGIN(HELP);
+}
+
+static void zconf_endhelp(void)
+{
+       zconflval.string = text;
+       BEGIN(INITIAL);
+}
+
+
+/*
+ * Try to open specified file with following names:
+ * ./name
+ * $(srctree)/name
+ * The latter is used when srctree is separate from objtree
+ * when compiling the kernel.
+ * Return NULL if file is not found.
+ */
+FILE *zconf_fopen(const char *name)
+{
+       char *env, fullname[PATH_MAX+1];
+       FILE *f;
+
+       f = fopen(name, "r");
+       if (!f && name[0] != '/') {
+               env = getenv(SRCTREE);
+               if (env) {
+                       sprintf(fullname, "%s/%s", env, name);
+                       f = fopen(fullname, "r");
+               }
+       }
+       return f;
+}
+
+void zconf_initscan(const char *name)
+{
+       yyin = zconf_fopen(name);
+       if (!yyin) {
+               printf("can't find file %s\n", name);
+               exit(1);
+       }
+
+       current_buf = malloc(sizeof(*current_buf));
+       memset(current_buf, 0, sizeof(*current_buf));
+
+       current_file = file_lookup(name);
+       current_file->lineno = 1;
+       current_file->flags = FILE_BUSY;
+}
+
+void zconf_nextfile(const char *name)
+{
+       struct file *file = file_lookup(name);
+       struct buffer *buf = malloc(sizeof(*buf));
+       memset(buf, 0, sizeof(*buf));
+
+       current_buf->state = YY_CURRENT_BUFFER;
+       yyin = zconf_fopen(name);
+       if (!yyin) {
+               printf("%s:%d: can't open file \"%s\"\n", zconf_curname(), zconf_lineno(), name);
+               exit(1);
+       }
+       yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
+       buf->parent = current_buf;
+       current_buf = buf;
+
+       if (file->flags & FILE_BUSY) {
+               printf("recursive scan (%s)?\n", name);
+               exit(1);
+       }
+       if (file->flags & FILE_SCANNED) {
+               printf("file %s already scanned?\n", name);
+               exit(1);
+       }
+       file->flags |= FILE_BUSY;
+       file->lineno = 1;
+       file->parent = current_file;
+       current_file = file;
+}
+
+static void zconf_endfile(void)
+{
+       struct buffer *parent;
+
+       current_file->flags |= FILE_SCANNED;
+       current_file->flags &= ~FILE_BUSY;
+       current_file = current_file->parent;
+
+       parent = current_buf->parent;
+       if (parent) {
+               fclose(yyin);
+               yy_delete_buffer(YY_CURRENT_BUFFER);
+               yy_switch_to_buffer(parent->state);
+       }
+       free(current_buf);
+       current_buf = parent;
+}
+
+int zconf_lineno(void)
+{
+       return current_pos.lineno;
+}
+
+char *zconf_curname(void)
+{
+       return current_pos.file ? current_pos.file->name : "<none>";
+}
diff --git a/scripts/kconfig/zconf.tab.c_shipped b/scripts/kconfig/zconf.tab.c_shipped
new file mode 100644 (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/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..09de114
--- /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"
+} | sort -r
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..2322fba
--- /dev/null
@@ -0,0 +1,302 @@
+#!/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
+    exitcode=$?
+    return $exitcode
+}
+
+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 -Wl,--sort-section option is not supported by older versions of ld
+SORT_SECTION=`check_cc "-Wl,--sort-section -Wl,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_SECTION=`(
+. ./.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_SECTION \
+       -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
+    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_SECTION \
+               -Wl,--start-group $O_FILES $A_FILES -Wl,--end-group \
+               $l_list
+       if test $? = 0; then
+           echo " Library $one is not needed"
+           LDLIBS="$without_one"
+           all_needed=false
+       else
+           echo " Library $one is needed"
+       fi
+    done
+    # All libs were needed, can't remove any
+    $all_needed && break
+    # If there is no space char, the list has just one lib.
+    # I'm not sure that in this case lib really is 100% needed.
+    # Let's try linking without it anyway... thus commented out.
+    #{ echo "$LDLIBS" | grep -q ' '; } || break
+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_SECTION \
+           -Wl,--start-group $O_FILES $A_FILES -Wl,--end-group \
+           $l_list \
+           -Wl,--warn-common \
+           -Wl,-Map -Wl,$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_SECTION \
+           -Wl,-T -Wl,busybox_ldscript \
+           -Wl,--start-group $O_FILES $A_FILES -Wl,--end-group \
+           $l_list \
+           -Wl,--warn-common \
+           -Wl,-Map -Wl,$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 -Wl,$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_SECTION \
+           -Wl,--start-group $O_FILES -Wl,--end-group \
+           -L"$sharedlib_dir" -lbusybox \
+           -Wl,--warn-common \
+           -Wl,-Map -Wl,$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_SECTION \
+               -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..f764056
--- /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..288e93a
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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>
+ */
+#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 change_filedir_context(
+               const char *fname,
+               struct stat *stbuf ATTRIBUTE_UNUSED,
+               void *userData ATTRIBUTE_UNUSED,
+               int depth ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, 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..a39ce6d
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * getenforce
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ */
+
+#include "libbb.h"
+
+int getenforce_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int getenforce_main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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..ea080d4
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * getsebool
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ */
+
+#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..c5b0e7a
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * load_policy
+ * Author: Yuichi Nakamura <ynakam@hitachisoft.jp>
+ */
+#include "libbb.h"
+
+int load_policy_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int load_policy_main(int argc, char **argv ATTRIBUTE_UNUSED)
+{
+       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..e0b374a
--- /dev/null
@@ -0,0 +1,86 @@
+/* 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>
+ *
+ */
+#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 ATTRIBUTE_UNUSED, 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..0f573d1
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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)
+ */
+#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 ATTRIBUTE_UNUSED, 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..c6e947c
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * selinuxenabled
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ */
+#include "libbb.h"
+
+int selinuxenabled_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int selinuxenabled_main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       return !is_selinux_enabled();
+}
diff --git a/selinux/sestatus.c b/selinux/sestatus.c
new file mode 100644 (file)
index 0000000..43e31d4
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * 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>
+ */
+
+#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[256];
+       FILE *fp;
+       int pc_ofs = 0, fc_ofs = 0, section = -1;
+
+       pc[0] = fc[0] = NULL;
+
+       fp = fopen("/etc/sestatus.conf", "rb");
+       if (fp == NULL)
+               return;
+
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               int i, c;
+
+               /* kills comments */
+               for (i = 0; (c = buf[i]) != '\0'; i++) {
+                       if (c == '#') {
+                               buf[i] = '\0';
+                               break;
+                       }
+               }
+               trim(buf);
+
+               if (buf[0] == '\0')
+                       continue;
+
+               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++] = strdup(buf);
+                               pc[pc_ofs] = NULL;
+                       } else if (section == 2 && fc_ofs < nfc - 1) {
+                               fc[fc_ofs++] = strdup(buf);
+                               fc[fc_ofs] = NULL;
+                       }
+               }
+       }
+       fclose(fp);
+}
+
+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 = ttyname(0);
+       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 ATTRIBUTE_UNUSED, 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..198324c
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * setenforce
+ *
+ * Based on libselinux 1.33.1
+ * Port to BusyBox  Hiroshi Shinji <shiroshi@my.email.ne.jp>
+ *
+ */
+
+#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..02bb911
--- /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 ATTRIBUTE_UNUSED, ...)
+{
+       /* 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 apply_spec(
+               const char *file,
+               struct stat *sb,
+               void *userData ATTRIBUTE_UNUSED,
+               int depth ATTRIBUTE_UNUSED)
+{
+       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(policyfile, "r");
+               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(out_filename, "w");
+               }
+       }
+       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(1);
+       }
+
+       if (input_filename) {
+               ssize_t len;
+               FILE *f = stdin;
+
+               if (NOT_LONE_CHAR(input_filename, '-'))
+                       f = xfopen(input_filename, "r");
+               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..83e70e2
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * setsebool
+ * Simple setsebool
+ * NOTE: -P option requires libsemanage, so this feature is
+ * omitted in this version
+ * Yuichi Nakamura <ynakam@hitachisoft.jp>
+ */
+
+#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..9328c91
--- /dev/null
@@ -0,0 +1,314 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Shells"
+
+choice
+       prompt "Choose your default shell"
+       default FEATURE_SH_IS_NONE
+       help
+         Choose a shell. The ash shell is the most bash compatible
+         and full featured one.
+
+config FEATURE_SH_IS_ASH
+       select ASH
+       bool "ash"
+
+config FEATURE_SH_IS_HUSH
+       select HUSH
+       bool "hush"
+
+####config FEATURE_SH_IS_LASH
+####   select LASH
+####   bool "lash"
+
+config FEATURE_SH_IS_MSH
+       select MSH
+       bool "msh"
+
+config FEATURE_SH_IS_NONE
+       bool "none"
+
+endchoice
+
+config ASH
+       bool "ash"
+       default n
+       select TEST
+       help
+         Tha 'ash' shell adds about 60k in the default configuration and is
+         the most complete and most pedantically correct shell included with
+         busybox.  This shell is actually a derivative of the Debian 'dash'
+         shell (by Herbert Xu), which was created by porting the 'ash' shell
+         (written by Kenneth Almquist) from NetBSD.
+
+comment "Ash Shell Options"
+       depends on ASH
+
+config ASH_JOB_CONTROL
+       bool "Job control"
+       default y
+       depends on ASH
+       help
+         Enable job control in the ash shell.
+
+config ASH_READ_NCHARS
+       bool "'read -n N' and 'read -s' support"
+       default n
+       depends on ASH
+       help
+         'read -n N' will return a value after N characters have been read.
+         'read -s' will read without echoing the user's input.
+
+config ASH_READ_TIMEOUT
+       bool "'read -t S' support."
+       default n
+       depends on ASH
+       help
+         'read -t S' will return a value after S seconds have passed.
+         This implementation will allow fractional seconds, expressed
+         as a decimal fraction, e.g. 'read -t 2.5 foo'.
+
+config ASH_ALIAS
+       bool "alias support"
+       default y
+       depends on ASH
+       help
+         Enable alias support in the ash shell.
+
+config ASH_MATH_SUPPORT
+       bool "Posix math support"
+       default y
+       depends on ASH
+       help
+         Enable math support in the ash shell.
+
+config ASH_MATH_SUPPORT_64
+       bool "Extend Posix math support to 64 bit"
+       default n
+       depends on ASH_MATH_SUPPORT
+       help
+         Enable 64-bit math support in the ash shell.  This will make
+         the shell slightly larger, but will allow computation with very
+         large numbers.
+
+config ASH_GETOPTS
+       bool "Builtin getopt to parse positional parameters"
+       default n
+       depends on ASH
+       help
+         Enable getopts builtin in the ash shell.
+
+config ASH_BUILTIN_ECHO
+       bool "Builtin version of 'echo'"
+       default y
+       select ECHO
+       depends on ASH
+       help
+         Enable support for echo, builtin to ash.
+
+config ASH_BUILTIN_TEST
+       bool "Builtin version of 'test'"
+       default y
+       select TEST
+       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 variable $RANDOM"
+       default n
+       depends on ASH
+       help
+         Enable pseudorandom generator and dynamic variable "$RANDOM".
+         Each read of "$RANDOM" will generate a new pseudorandom value.
+         You can reset the generator by using a specified start value.
+         After "unset RANDOM" then generator will switch off and this
+         variable will no longer have special treatment.
+
+config ASH_EXPAND_PRMT
+       bool "Expand prompt string"
+       default n
+       depends on ASH
+       help
+         "PS#" may be contain volatile content, such as backquote commands.
+         This option recreates the prompt string from the environment
+         variable each time it is displayed.
+
+config HUSH
+       bool "hush"
+       default n
+       select TRUE
+       select FALSE
+       select TEST
+       select ECHO
+       help
+         hush is a very small shell (just 18k) and it has fairly complete
+         Bourne shell grammar.  It even handles all the normal flow control
+         options such as if/then/elif/else/fi, for/in/do/done, while loops,
+         etc.
+
+         It does not handle case/esac, select, function, here documents ( <<
+         word ), arithmetic expansion, aliases, brace expansion, tilde
+         expansion, &> and >& redirection of stdout+stderr, etc.
+
+config 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 LASH
+       bool "lash"
+       default n
+       select HUSH
+       help
+         lash is deprecated and will be removed, please migrate to hush.
+
+
+config MSH
+       bool "msh"
+       default n
+       select TRUE
+       select FALSE
+       select TEST
+       help
+         The minix shell (adds just 30k) is quite complete and handles things
+         like for/do/done, case/esac and all the things you expect a Bourne
+         shell to do.  It is not always pedantically correct about Bourne
+         shell grammar (try running the shell testscript "tests/sh.testcases"
+         on it and compare vs bash) but for most things it works quite well.
+         It also uses only vfork, so it can be used on uClinux systems.
+
+comment "Bourne Shell Options"
+       depends on MSH || LASH || HUSH || ASH
+
+config FEATURE_SH_EXTRA_QUIET
+       bool "Hide message on interactive shell startup"
+       default n
+       depends on MSH || LASH || HUSH || ASH
+       help
+         Remove the busybox introduction when starting a shell.
+
+config FEATURE_SH_STANDALONE
+       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 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..deedc24
--- /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_ASH)      += ash.o ash_ptr_hack.o
+lib-$(CONFIG_HUSH)     += hush.o
+lib-$(CONFIG_MSH)      += msh.o
+lib-$(CONFIG_CTTYHACK) += cttyhack.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..d9ce202
--- /dev/null
@@ -0,0 +1,13159 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ash shell port for busybox
+ *
+ * Copyright (c) 1989, 1991, 1993, 1994
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au>
+ * was re-ported from NetBSD and debianized.
+ *
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Original BSD copyright notice is retained at the end of this file.
+ */
+
+/*
+ * rewrite arith.y to micro stack based cryptic algorithm by
+ * Copyright (c) 2001 Aaron Lehmann <aaronl@vitelus.com>
+ *
+ * Modified by Paul Mundt <lethal@linux-sh.org> (c) 2004 to support
+ * dynamic variables.
+ *
+ * Modified by Vladimir Oleynik <dzo@simtreas.ru> (c) 2001-2005 to be
+ * used in busybox and size optimizations,
+ * rewrote arith (see notes to this), added locale support,
+ * rewrote dynamic variables.
+ *
+ */
+
+/*
+ * The follow should be set to reflect the type of system you have:
+ *      JOBS -> 1 if you have Berkeley job control, 0 otherwise.
+ *      define SYSV if you are running under System V.
+ *      define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
+ *      define DEBUG=2 to compile in and turn on debugging.
+ *
+ * When debugging is on, debugging info will be written to ./trace and
+ * a quit signal will generate a core dump.
+ */
+#define DEBUG 0
+#define 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 */
+#include <paths.h>
+#include <setjmp.h>
+#include <fnmatch.h>
+#if JOBS || ENABLE_ASH_READ_NCHARS
+#include <termios.h>
+#endif
+
+#if defined(__uClinux__)
+#error "Do not even bother, ash will not run on uClinux"
+#endif
+
+
+/* ============ Hash table sizes. Configurable. */
+
+#define VTABSIZE 39
+#define ATABSIZE 39
+#define CMDTABLESIZE 31         /* should be prime */
+
+
+/* ============ Misc helpers */
+
+#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
+
+/* C99 say: "char" declaration may be signed or unsigned default */
+#define signed_char2int(sc) ((int)((signed char)sc))
+
+
+/* ============ 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) };
+
+static char optlist[NOPTS] ALIGN1;
+
+#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
+
+
+/* ============ 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
+ * 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 */
+       volatile /*sig_atomic_t*/ smallint intpending; /* 1 = got SIGINT */
+       /* last pending signal */
+       volatile /*sig_atomic_t*/ smallint pendingsig;
+       smallint exception; /* 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) */
+
+       /* trap handler commands */
+       char *trap[NSIG];
+       smallint isloginsh;
+       char nullstr[1];                /* zero length string */
+       /*
+        * 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 */
+#define S_RESET 5               /* temporary - to reset a hard ignored sig */
+
+       /* indicates specified signal received */
+       char gotsig[NSIG - 1];
+};
+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         (G_misc.exception        )
+#define suppressint       (G_misc.suppressint      )
+#define intpending        (G_misc.intpending       )
+//#define exsig             (G_misc.exsig            )
+#define pendingsig        (G_misc.pendingsig       )
+#define trap      (G_misc.trap     )
+#define isloginsh (G_misc.isloginsh)
+#define nullstr   (G_misc.nullstr  )
+#define sigmode   (G_misc.sigmode  )
+#define gotsig    (G_misc.gotsig   )
+#define INIT_G_misc() do { \
+       (*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \
+       barrier(); \
+       curdir = nullstr; \
+       physdir = nullstr; \
+} while (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".
+ */
+static void raise_exception(int) ATTRIBUTE_NORETURN;
+static void
+raise_exception(int e)
+{
+#if DEBUG
+       if (exception_handler == NULL)
+               abort();
+#endif
+       INT_OFF;
+       exception = e;
+       longjmp(exception_handler->loc, 1);
+}
+
+/*
+ * Called from trap.c when a SIGINT is received.  (If the user specifies
+ * that SIGINT is to be trapped or ignored using the trap builtin, then
+ * this routine is not called.)  Suppressint is nonzero when interrupts
+ * are held using the INT_OFF macro.  (The test for iflag is just
+ * defensive programming.)
+ */
+static void raise_interrupt(void) ATTRIBUTE_NORETURN;
+static void
+raise_interrupt(void)
+{
+       int i;
+
+       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() */
+
+       i = EXSIG;
+       if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
+               if (!(rootshell && iflag)) {
+                       /* Kill ourself with SIGINT */
+                       signal(SIGINT, SIG_DFL);
+                       raise(SIGINT);
+               }
+               i = EXINT;
+       }
+       raise_exception(i);
+       /* NOTREACHED */
+}
+
+#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
+static void
+int_on(void)
+{
+       if (--suppressint == 0 && intpending) {
+               raise_interrupt();
+       }
+}
+#define INT_ON int_on()
+static void
+force_int_on(void)
+{
+       suppressint = 0;
+       if (intpending)
+               raise_interrupt();
+}
+#define FORCE_INT_ON force_int_on()
+#else
+#define INT_ON \
+       do { \
+               xbarrier(); \
+               if (--suppressint == 0 && intpending) \
+                       raise_interrupt(); \
+       } while (0)
+#define FORCE_INT_ON \
+       do { \
+               xbarrier(); \
+               suppressint = 0; \
+               if (intpending) \
+                       raise_interrupt(); \
+       } while (0)
+#endif /* ASH_OPTIMIZE_FOR_SIZE */
+
+#define SAVE_INT(v) ((v) = suppressint)
+
+#define RESTORE_INT(v) \
+       do { \
+               xbarrier(); \
+               suppressint = (v); \
+               if (suppressint == 0 && intpending) \
+                       raise_interrupt(); \
+       } while (0)
+
+/*
+ * Ignore a signal. Only one usage site - in forkchild()
+ */
+static void
+ignoresig(int signo)
+{
+       if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
+               signal(signo, SIG_IGN);
+       }
+       sigmode[signo - 1] = S_HARD_IGN;
+}
+
+/*
+ * Signal handler. Only one usage site - in setsignal()
+ */
+static void
+onsig(int signo)
+{
+       gotsig[signo - 1] = 1;
+       pendingsig = signo;
+
+       if ( /* exsig || */ (signo == SIGINT && !trap[SIGINT])) {
+               if (!suppressint) {
+                       pendingsig = 0;
+                       raise_interrupt(); /* does not return */
+               }
+               intpending = 1;
+       }
+}
+
+
+/* ============ 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} */
+
+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
+#define NCLOBBER 17
+#define NFROM 18
+#define NFROMTO 19
+#define NAPPEND 20
+#define NTOFD 21
+#define NFROMFD 22
+#define NHERE 23
+#define NXHERE 24
+#define NNOT 25
+
+union node;
+
+struct ncmd {
+       int type;
+       union node *assign;
+       union node *args;
+       union node *redirect;
+};
+
+struct npipe {
+       int type;
+       int backgnd;
+       struct nodelist *cmdlist;
+};
+
+struct nredir {
+       int type;
+       union node *n;
+       union node *redirect;
+};
+
+struct nbinary {
+       int type;
+       union node *ch1;
+       union node *ch2;
+};
+
+struct nif {
+       int type;
+       union node *test;
+       union node *ifpart;
+       union node *elsepart;
+};
+
+struct nfor {
+       int type;
+       union node *args;
+       union node *body;
+       char *var;
+};
+
+struct ncase {
+       int type;
+       union node *expr;
+       union node *cases;
+};
+
+struct nclist {
+       int type;
+       union node *next;
+       union node *pattern;
+       union node *body;
+};
+
+struct narg {
+       int type;
+       union node *next;
+       char *text;
+       struct nodelist *backquote;
+};
+
+struct nfile {
+       int type;
+       union node *next;
+       int fd;
+       union node *fname;
+       char *expfname;
+};
+
+struct ndup {
+       int type;
+       union node *next;
+       int fd;
+       int dupfd;
+       union node *vname;
+};
+
+struct nhere {
+       int type;
+       union node *next;
+       int fd;
+       union node *doc;
+};
+
+struct nnot {
+       int type;
+       union node *com;
+};
+
+union node {
+       int type;
+       struct ncmd ncmd;
+       struct npipe npipe;
+       struct nredir nredir;
+       struct nbinary nbinary;
+       struct nif nif;
+       struct nfor nfor;
+       struct ncase ncase;
+       struct nclist nclist;
+       struct narg narg;
+       struct nfile nfile;
+       struct ndup ndup;
+       struct nhere nhere;
+       struct nnot nnot;
+};
+
+struct nodelist {
+       struct nodelist *next;
+       union node *n;
+};
+
+struct funcnode {
+       int count;
+       union node n;
+};
+
+/*
+ * 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;
+       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;
+       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;
+               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.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);
+}
+
+#define TRACE(param)    trace_printf param
+#define TRACEV(param)   trace_vprintf param
+
+#else
+
+#define TRACE(param)
+#define TRACEV(param)
+
+#endif /* DEBUG */
+
+
+/* ============ Parser data */
+
+/*
+ * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
+ */
+struct strlist {
+       struct strlist *next;
+       char *text;
+};
+
+#if ENABLE_ASH_ALIAS
+struct alias;
+#endif
+
+struct strpush {
+       struct strpush *prev;   /* preceding string on stack */
+       char *prevstring;
+       int prevnleft;
+#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 nleft;              /* number of chars left in this line */
+       int lleft;              /* number of chars left in this buffer */
+       char *nextc;            /* next char in buffer */
+       char *buf;              /* input buffer */
+       struct strpush *strpush; /* for pushing strings at this level */
+       struct strpush basestrpush; /* so pushing one is fast */
+};
+
+static struct parsefile basepf;         /* top level input file */
+static struct parsefile *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 int 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 || 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) ATTRIBUTE_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 *, ...) ATTRIBUTE_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 ash_msg_and_raise(int, const char *, ...) ATTRIBUTE_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(2, "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;
+               int 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 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 stackblock() + len;
+}
+
+static char *
+stack_nputstr(const char *s, size_t n, char *p)
+{
+       p = makestrspace(n, p);
+       p = 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 = 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 = 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 return 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') {
+               p = *argptr;
+               if (p == NULL || *p != '-' || *++p == '\0')
+                       return '\0';
+               argptr++;
+               if (LONE_DASH(p))        /* check for "--" */
+                       return '\0';
+       }
+       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++) == NULL)
+                       ash_msg_and_raise_error("no arg for -%c option", c);
+               optionarg = p;
+               p = NULL;
+       }
+       optptr = p;
+       return c;
+}
+
+
+/* ============ Math support definitions */
+
+#if ENABLE_ASH_MATH_SUPPORT_64
+typedef int64_t arith_t;
+#define arith_t_type long long
+#else
+typedef long arith_t;
+#define arith_t_type long
+#endif
+
+#if ENABLE_ASH_MATH_SUPPORT
+static arith_t dash_arith(const char *);
+static arith_t arith(const char *expr, int *perrcode);
+#endif
+
+#if ENABLE_ASH_RANDOM_SUPPORT
+static unsigned long rseed;
+#ifndef DYNAMIC_VAR
+#define DYNAMIC_VAR
+#endif
+#endif
+
+
+/* ============ Shell variables */
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
+struct redirtab {
+       struct redirtab *next;
+       int renamed[10];
+       int nullredirs;
+};
+
+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)
+{
+       char **ap;
+
+       if (param->malloced) {
+               for (ap = param->p; *ap; ap++)
+                       free(*ap);
+               free(param->p);
+       }
+}
+
+#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 */
+#ifdef DYNAMIC_VAR
+# 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 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 { \
+       int 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 char *
+lookupvar(const char *name)
+{
+       struct var *v;
+
+       v = *findvar(hashvar(name), name);
+       if (v) {
+#ifdef DYNAMIC_VAR
+       /*
+        * Dynamic variables are implemented roughly the same way they are
+        * in bash. Namely, they're "special" so long as they aren't unset.
+        * As soon as they're unset, they're no longer dynamic, and dynamic
+        * lookup will no longer happen at that point. -- PFM.
+        */
+               if ((v->flags & VDYNAMIC))
+                       (*v->func)(NULL);
+#endif
+               if (!(v->flags & VUNSET))
+                       return strchrnul(v->text, '=') + 1;
+       }
+       return NULL;
+}
+
+/*
+ * Search the environment of a builtin command.
+ */
+static char *
+bltinlookup(const char *name)
+{
+       struct strlist *sp;
+
+       for (sp = cmdenviron; sp; sp = sp->next) {
+               if (varequal(sp->text, name))
+                       return strchrnul(sp->text, '=') + 1;
+       }
+       return lookupvar(name);
+}
+
+/*
+ * 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 = memcpy(nameeq, name, namelen) + namelen;
+       if (val) {
+               *p++ = '=';
+               p = 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;
+#ifdef DYNAMIC_VAR
+               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++);
+       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 = 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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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
+#define basebuf bb_common_bufsiz1       /* buffer for top level input file */
+
+/* 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_ASH_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 /* ASH_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
+#define U_C(c) ((unsigned char)(c))
+
+       if ((unsigned char)c >= (unsigned char)(CTLESC)
+        && (unsigned char)c <= (unsigned char)(CTLQUOTEMARK)
+       ) {
+               return CCTL;
+       } else {
+               s = strchr(spec_symbls, c);
+               if (s == NULL || *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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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 pid_t backgndpid;        /* pid of last background process */
+static smallint job_warning;    /* user was warned about stopped jobs (can be 2, 1 or 0). */
+
+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 { jobctl = 0 };
+#define setjobctl(on) do {} while (0)
+#else
+static smallint jobctl;              /* true if doing job control */
+static void setjobctl(int);
+#endif
+
+/*
+ * Set the signal handler for the specified signal.  The routine figures
+ * out what it should be set to.
+ */
+static void
+setsignal(int signo)
+{
+       int action;
+       char *t, tsig;
+       struct sigaction act;
+
+       t = trap[signo];
+       action = S_IGN;
+       if (t == NULL)
+               action = S_DFL;
+       else if (*t != '\0')
+               action = S_CATCH;
+       if (rootshell && action == S_DFL) {
+               switch (signo) {
+               case SIGINT:
+                       if (iflag || minusc || sflag == 0)
+                               action = S_CATCH;
+                       break;
+               case SIGQUIT:
+#if DEBUG
+                       if (debug)
+                               break;
+#endif
+                       /* FALLTHROUGH */
+               case SIGTERM:
+                       if (iflag)
+                               action = S_IGN;
+                       break;
+#if JOBS
+               case SIGTSTP:
+               case SIGTTOU:
+                       if (mflag)
+                               action = S_IGN;
+                       break;
+#endif
+               }
+       }
+
+       t = &sigmode[signo - 1];
+       tsig = *t;
+       if (tsig == 0) {
+               /*
+                * current setting unknown
+                */
+               if (sigaction(signo, NULL, &act) == -1) {
+                       /*
+                        * Pretend it worked; maybe we should give a warning
+                        * here, but other shells don't. We don't alter
+                        * sigmode, so that we retry every time.
+                        */
+                       return;
+               }
+               tsig = S_RESET; /* force to be set */
+               if (act.sa_handler == SIG_IGN) {
+                       tsig = S_HARD_IGN;
+                       if (mflag
+                        && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
+                       ) {
+                               tsig = S_IGN;   /* don't hard ignore these */
+                       }
+               }
+       }
+       if (tsig == S_HARD_IGN || tsig == action)
+               return;
+       act.sa_handler = SIG_DFL;
+       switch (action) {
+       case S_CATCH:
+               act.sa_handler = onsig;
+               break;
+       case S_IGN:
+               act.sa_handler = SIG_IGN;
+               break;
+       }
+       *t = action;
+       act.sa_flags = 0;
+       sigfillset(&act.sa_mask);
+       sigaction_set(signo, &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;
+static int ttyfd = -1;
+#endif
+/* array of jobs */
+static struct job *jobtab;
+/* size of array */
+static unsigned njobs;
+/* current job */
+static struct job *curjob;
+/* number of presumed living untracked jobs */
+static int jobless;
+
+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)) {
+               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("cannot 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 == 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;
+       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 ATTRIBUTE_UNUSED, 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;
+}
+
+/*
+ * Do a wait system call.  If job control is compiled in, we accept
+ * stopped processes.  If block is zero, we return a value of zero
+ * rather than blocking.
+ *
+ * System V doesn't have a non-blocking wait system call.  It does
+ * have a SIGCLD signal that is sent to a process when one of it's
+ * children dies.  The obvious way to use SIGCLD would be to install
+ * a handler for SIGCLD which simply bumped a counter when a SIGCLD
+ * was received, and have waitproc bump another counter when it got
+ * the status of a process.  Waitproc would then know that a wait
+ * system call would not block if the two counters were different.
+ * This approach doesn't work because if a process has children that
+ * have not been waited for, System V will send it a SIGCLD when it
+ * installs a signal handler for SIGCLD.  What this means is that when
+ * a child exits, the shell will be sent SIGCLD signals continuously
+ * until is runs out of stack space, unless it does a wait call before
+ * restoring the signal handler.  The code below takes advantage of
+ * this (mis)feature by installing a signal handler for SIGCLD and
+ * then checking to see whether it was called.  If there are any
+ * children to be waited for, it will be.
+ *
+ * If neither SYSV nor BSD is defined, we don't implement nonblocking
+ * waits at all.  In this case, the user will not be informed when
+ * a background process until the next time she runs a real program
+ * (as opposed to running a builtin command or just typing return),
+ * and the jobs command may give out of date information.
+ */
+static int
+waitproc(int wait_flags, int *status)
+{
+#if JOBS
+       if (jobctl)
+               wait_flags |= WUNTRACED;
+#endif
+       /* NB: _not_ safe_waitpid, we need to detect EINTR */
+       return waitpid(-1, status, wait_flags);
+}
+
+/*
+ * Wait for a process to terminate.
+ */
+static int
+dowait(int wait_flags, struct job *job)
+{
+       int pid;
+       int status;
+       struct job *jp;
+       struct job *thisjob;
+       int state;
+
+       TRACE(("dowait(%d) called\n", wait_flags));
+       pid = waitproc(wait_flags, &status);
+       TRACE(("wait returns pid=%d, status=%d\n", pid, status));
+       if (pid <= 0) {
+               /* If we were doing blocking wait and (probably) got EINTR,
+                * check for pending sigs received while waiting.
+                * (NB: can be moved into callers if needed) */
+               if (wait_flags == DOWAIT_BLOCK && pendingsig)
+                       raise_exception(EXSIG);
+               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;
+}
+
+#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));
+
+       /* If not even one job changed, there is nothing to do */
+       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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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;
+                       }
+                       dowait(DOWAIT_BLOCK, 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)
+                       dowait(DOWAIT_BLOCK, 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 (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 (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)
+{
+       const char *p, *str;
+       char c, cc[2] = " ";
+       char *nextc;
+       int subtype = 0;
+       int quoted = 0;
+       static const char vstype[VSTYPE + 1][4] = {
+               "", "}", "-", "+", "?", "=",
+               "%", "%%", "#", "##"
+       };
+
+       nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
+       p = s;
+       while ((c = *p++) != 0) {
+               str = 0;
+               switch (c) {
+               case CTLESC:
+                       c = *p++;
+                       break;
+               case CTLVAR:
+                       subtype = *p++;
+                       if ((subtype & VSTYPE) == VSLENGTH)
+                               str = "${#";
+                       else
+                               str = "${";
+                       if (!(subtype & VSQUOTE) == !(quoted & 1))
+                               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_ASH_MATH_SUPPORT
+               case CTLARI:
+                       str = "$((";
+                       goto dostr;
+               case CTLENDARI:
+                       str = "))";
+                       goto dostr;
+#endif
+               case CTLQUOTEMARK:
+                       quoted ^= 1;
+                       c = '"';
+                       break;
+               case '=':
+                       if (subtype == 0)
+                               break;
+                       if ((subtype & VSTYPE) != VSNORMAL)
+                               quoted <<= 1;
+                       str = vstype[subtype & VSTYPE];
+                       if (subtype & VSNUL)
+                               c = ':';
+                       else
+                               goto checkstr;
+                       break;
+               case '\'':
+               case '\\':
+               case '"':
+               case '$':
+                       /* These can only happen inside quotes */
+                       cc[0] = c;
+                       str = cc;
+                       c = '\\';
+                       break;
+               default:
+                       break;
+               }
+               USTPUTC(c, nextc);
+ checkstr:
+               if (!str)
+                       continue;
+ dostr:
+               while ((c = *str++)) {
+                       USTPUTC(c, nextc);
+               }
+       }
+       if (quoted & 1) {
+               USTPUTC('"', nextc);
+       }
+       *nextc = 0;
+       cmdnextc = nextc;
+}
+
+/* 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;
+       char s[2];
+
+       if (!n)
+               return;
+       switch (n->type) {
+       default:
+#if DEBUG
+               abort();
+#endif
+       case NPIPE:
+               lp = n->npipe.cmdlist;
+               for (;;) {
+                       cmdtxt(lp->n);
+                       lp = lp->next;
+                       if (!lp)
+                               break;
+                       cmdputs(" | ");
+               }
+               break;
+       case NSEMI:
+               p = "; ";
+               goto binop;
+       case NAND:
+               p = " && ";
+               goto binop;
+       case NOR:
+               p = " || ";
+ binop:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs(p);
+               n = n->nbinary.ch2;
+               goto donode;
+       case NREDIR:
+       case NBACKGND:
+               n = n->nredir.n;
+               goto donode;
+       case NNOT:
+               cmdputs("!");
+               n = n->nnot.com;
+ donode:
+               cmdtxt(n);
+               break;
+       case NIF:
+               cmdputs("if ");
+               cmdtxt(n->nif.test);
+               cmdputs("; then ");
+               n = n->nif.ifpart;
+               if (n->nif.elsepart) {
+                       cmdtxt(n);
+                       cmdputs("; else ");
+                       n = n->nif.elsepart;
+               }
+               p = "; fi";
+               goto dotail;
+       case NSUBSHELL:
+               cmdputs("(");
+               n = n->nredir.n;
+               p = ")";
+               goto dotail;
+       case NWHILE:
+               p = "while ";
+               goto until;
+       case NUNTIL:
+               p = "until ";
+ until:
+               cmdputs(p);
+               cmdtxt(n->nbinary.ch1);
+               n = n->nbinary.ch2;
+               p = "; done";
+ dodo:
+               cmdputs("; do ");
+ dotail:
+               cmdtxt(n);
+               goto dotail2;
+       case NFOR:
+               cmdputs("for ");
+               cmdputs(n->nfor.var);
+               cmdputs(" in ");
+               cmdlist(n->nfor.args, 1);
+               n = n->nfor.body;
+               p = "; done";
+               goto dodo;
+       case NDEFUN:
+               cmdputs(n->narg.text);
+               p = "() { ... }";
+               goto dotail2;
+       case NCMD:
+               cmdlist(n->ncmd.args, 1);
+               cmdlist(n->ncmd.redirect, 0);
+               break;
+       case NARG:
+               p = n->narg.text;
+ dotail2:
+               cmdputs(p);
+               break;
+       case NHERE:
+       case NXHERE:
+               p = "<<...";
+               goto dotail2;
+       case NCASE:
+               cmdputs("case ");
+               cmdputs(n->ncase.expr->narg.text);
+               cmdputs(" in ");
+               for (np = n->ncase.cases; np; np = np->nclist.next) {
+                       cmdtxt(np->nclist.pattern);
+                       cmdputs(") ");
+                       cmdtxt(np->nclist.body);
+                       cmdputs(";; ");
+               }
+               p = "esac";
+               goto dotail2;
+       case NTO:
+               p = ">";
+               goto redir;
+       case NCLOBBER:
+               p = ">|";
+               goto redir;
+       case NAPPEND:
+               p = ">>";
+               goto redir;
+       case NTOFD:
+               p = ">&";
+               goto redir;
+       case NFROM:
+               p = "<";
+               goto redir;
+       case NFROMFD:
+               p = "<&";
+               goto redir;
+       case NFROMTO:
+               p = "<>";
+ redir:
+               s[0] = n->nfile.fd + '0';
+               s[1] = '\0';
+               cmdputs(s);
+               cmdputs(p);
+               if (n->type == NTOFD || n->type == NFROMFD) {
+                       s[0] = n->ndup.dupfd + '0';
+                       p = s;
+                       goto dotail2;
+               }
+               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++;
+
+       closescript();
+       clear_traps();
+#if JOBS
+       /* do job control only in root shell */
+       jobctl = 0;
+       if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) {
+               pid_t pgrp;
+
+               if (jp->nprocs == 0)
+                       pgrp = getpid();
+               else
+                       pgrp = jp->ps[0].pid;
+               /* This can fail because we are doing it in the parent also */
+               (void)setpgid(0, pgrp);
+               if (mode == FORK_FG)
+                       xtcsetpgrp(ttyfd, pgrp);
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+       } else
+#endif
+       if (mode == FORK_BG) {
+               ignoresig(SIGINT);
+               ignoresig(SIGQUIT);
+               if (jp->nprocs == 0) {
+                       close(0);
+                       if (open(bb_dev_null, O_RDONLY) != 0)
+                               ash_msg_and_raise_error("can't open %s", bb_dev_null);
+               }
+       }
+       if (!oldlvl && iflag) {
+               setsignal(SIGINT);
+               setsignal(SIGQUIT);
+               setsignal(SIGTERM);
+       }
+       for (jp = curjob; jp; jp = jp->prev_job)
+               freejob(jp);
+       jobless = 0;
+}
+
+/* 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 (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("cannot fork");
+       }
+       if (pid == 0)
+               forkchild(jp, /*n,*/ mode);
+       else
+               forkparent(jp, n, mode, pid);
+       return pid;
+}
+
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process is
+ * running interrupts generated by the user are sent to the child but not
+ * to the shell.  This means that an infinite loop started by an inter-
+ * active user may be hard to kill.  With job control turned off, an
+ * interactive user may place an interactive program inside a loop.  If
+ * the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop.  The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * foreground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ *
+ * Called with interrupts off.
+ */
+static int
+waitforjob(struct job *jp)
+{
+       int st;
+
+       TRACE(("waitforjob(%%%d) called\n", jobno(jp)));
+       while (jp->state == JOBRUNNING) {
+               dowait(DOWAIT_BLOCK, jp);
+       }
+       st = getstatus(jp);
+#if JOBS
+       if (jp->jobctl) {
+               xtcsetpgrp(ttyfd, rootpid);
+               /*
+                * This is truly gross.
+                * If we're doing job control, then we did a TIOCSPGRP which
+                * caused us (the shell) to no longer be in the controlling
+                * session -- so we wouldn't have seen any ^C/SIGINT.  So, we
+                * intuit from the subprocess exit status whether a SIGINT
+                * occurred, and if so interrupt ourselves.  Yuck.  - mycroft
+                */
+               if (jp->sigint) /* 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 */
+#ifndef PIPE_BUF
+# define PIPESIZE 4096          /* amount of buffering in a pipe */
+#else
+# define PIPESIZE PIPE_BUF
+#endif
+
+/*
+ * Open a file in noclobber mode.
+ * The code was copied from bash.
+ */
+static int
+noclobberopen(const char *fname)
+{
+       int r, fd;
+       struct stat finfo, finfo2;
+
+       /*
+        * If the file exists and is a regular file, return an error
+        * immediately.
+        */
+       r = stat(fname, &finfo);
+       if (r == 0 && S_ISREG(finfo.st_mode)) {
+               errno = EEXIST;
+               return -1;
+       }
+
+       /*
+        * If the file was not present (r != 0), make sure we open it
+        * exclusively so that if it is created before we open it, our open
+        * will fail.  Make sure that we do not truncate an existing file.
+        * Note that we don't turn on O_EXCL unless the stat failed -- if the
+        * file was not a regular file, we leave O_EXCL off.
+        */
+       if (r != 0)
+               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+       fd = open(fname, O_WRONLY|O_CREAT, 0666);
+
+       /* If the open failed, return the file descriptor right away. */
+       if (fd < 0)
+               return fd;
+
+       /*
+        * OK, the open succeeded, but the file may have been changed from a
+        * non-regular file to a regular file between the stat and the open.
+        * We are assuming that the O_EXCL open handles the case where FILENAME
+        * did not exist and is symlinked to an existing file between the stat
+        * and open.
+        */
+
+       /*
+        * If we can open it and fstat the file descriptor, and neither check
+        * revealed that it was a regular file, and the file has not been
+        * replaced, return the file descriptor.
+        */
+       if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode)
+        && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
+               return fd;
+
+       /* The file has been replaced.  badness. */
+       close(fd);
+       errno = EEXIST;
+       return -1;
+}
+
+/*
+ * Handle here documents.  Normally we fork off a process to write the
+ * data to a pipe.  If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+/* 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 <= PIPESIZE) {
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+                       goto out;
+               }
+       }
+       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+               close(pip[0]);
+               signal(SIGINT, SIG_IGN);
+               signal(SIGQUIT, SIG_IGN);
+               signal(SIGHUP, SIG_IGN);
+#ifdef SIGTSTP
+               signal(SIGTSTP, SIG_IGN);
+#endif
+               signal(SIGPIPE, SIG_DFL);
+               if (redir->type == NHERE)
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+               else
+                       expandhere(redir->nhere.doc, pip[1]);
+               _exit(0);
+       }
+ out:
+       close(pip[1]);
+       return pip[0];
+}
+
+static int
+openredirect(union node *redir)
+{
+       char *fname;
+       int f;
+
+       switch (redir->nfile.type) {
+       case NFROM:
+               fname = redir->nfile.expfname;
+               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:
+               /* 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. */
+       case NTOFD:
+       case NFROMFD:
+               f = -1;
+               break;
+       case NHERE:
+       case NXHERE:
+               f = openhere(redir);
+               break;
+       }
+
+       return f;
+ ecreate:
+       ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "nonexistent directory"));
+ eopen:
+       ash_msg_and_raise_error("cannot 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.
+ */
+static int
+copyfd(int from, int to)
+{
+       int newfd;
+
+       newfd = fcntl(from, F_DUPFD, to);
+       if (newfd < 0) {
+               if (errno == EMFILE)
+                       return EMPTY;
+               ash_msg_and_raise_error("%d: %m", from);
+       }
+       return newfd;
+}
+
+static void
+dupredirect(union node *redir, int f)
+{
+       int fd = redir->nfile.fd;
+
+       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
+                       copyfd(redir->ndup.dupfd, fd);
+               }
+               return;
+       }
+
+       if (f != fd) {
+               copyfd(f, fd);
+               close(f);
+       }
+}
+
+/*
+ * 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)
+{
+       union node *n;
+       struct redirtab *sv;
+       int i;
+       int fd;
+       int newfd;
+
+       g_nullredirs++;
+       if (!redir) {
+               return;
+       }
+       sv = NULL;
+       INT_OFF;
+       if (flags & REDIR_PUSH) {
+               sv = ckmalloc(sizeof(*sv));
+               sv->next = redirlist;
+               redirlist = sv;
+               sv->nullredirs = g_nullredirs - 1;
+               for (i = 0; i < 10; i++)
+                       sv->renamed[i] = EMPTY;
+               g_nullredirs = 0;
+       }
+       n = redir;
+       do {
+               fd = n->nfile.fd;
+               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD)
+                && n->ndup.dupfd == fd)
+                       continue; /* redirect from/to same file descriptor */
+
+               newfd = openredirect(n);
+               if (fd == newfd) {
+                       /* Descriptor wasn't open before redirect.
+                        * Mark it for close in the future */
+                       if (sv && sv->renamed[fd] == EMPTY)
+                               sv->renamed[fd] = CLOSED;
+                       continue;
+               }
+               if (sv && sv->renamed[fd] == EMPTY) {
+                       i = fcntl(fd, F_DUPFD, 10);
+
+                       if (i == -1) {
+                               i = errno;
+                               if (i != EBADF) {
+                                       close(newfd);
+                                       errno = i;
+                                       ash_msg_and_raise_error("%d: %m", fd);
+                                       /* NOTREACHED */
+                               }
+                       } else {
+                               sv->renamed[fd] = i;
+                               close(fd);
+                       }
+               } else {
+                       close(fd);
+               }
+               dupredirect(n, newfd);
+       } while ((n = n->nfile.next));
+       INT_ON;
+       if ((flags & REDIR_SAVEFD2) && sv && sv->renamed[2] >= 0)
+               preverrout_fd = sv->renamed[2];
+}
+
+/*
+ * Undo the effects of the last redirection.
+ */
+static void
+popredir(int drop)
+{
+       struct redirtab *rp;
+       int i;
+
+       if (--g_nullredirs >= 0)
+               return;
+       INT_OFF;
+       rp = redirlist;
+       for (i = 0; i < 10; i++) {
+               if (rp->renamed[i] == CLOSED) {
+                       if (!drop)
+                               close(i);
+                       continue;
+               }
+               if (rp->renamed[i] != EMPTY) {
+                       if (!drop) {
+                               close(i);
+                               copyfd(rp->renamed[i], i);
+                       }
+                       close(rp->renamed[i]);
+               }
+       }
+       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);
+       }
+}
+
+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);
+       err = setjmp(jmploc.loc) * 2;
+       if (!err) {
+               exception_handler = &jmploc;
+               redirect(redir, flags);
+       }
+       exception_handler = savehandler;
+       if (err && exception != 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.
+ */
+
+/*
+ * 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);
+#if ENABLE_ASH_MATH_SUPPORT_64
+       len = fmtstr(expdest, 32, "%lld", (long long) num);
+#else
+       len = fmtstr(expdest, 32, "%ld", num);
+#endif
+       STADJUST(len, expdest);
+       return len;
+}
+
+static 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 = 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 */
+       char *buf;              /* buffer */
+       int nleft;              /* number of chars in buffer */
+       struct job *jp;         /* job structure for command */
+};
+
+/* These forward decls are needed to use "eval" code for backticks handling: */
+static int 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);
+                               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_ASH_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(dash_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_ASH_MATH_SUPPORT
+               CTLENDARI,
+#endif
+               0
+       };
+       const char *reject = spclchars;
+       int c;
+       int quotes = flag & (EXP_FULL | EXP_CASE);      /* do CTLESC */
+       int breakall = flag & EXP_WORD;
+       int inquotes;
+       size_t length;
+       int startloc;
+
+       if (!(flag & EXP_VARTILDE)) {
+               reject += 2;
+       } else if (flag & EXP_VARTILDE2) {
+               reject++;
+       }
+       inquotes = 0;
+       length = 0;
+       if (flag & EXP_TILDE) {
+               char *q;
+
+               flag &= ~EXP_TILDE;
+ tilde:
+               q = p;
+               if (*q == CTLESC && (flag & EXP_QWORD))
+                       q++;
+               if (*q == '~')
+                       p = exptilde(p, q, flag);
+       }
+ start:
+       startloc = expdest - (char *)stackblock();
+       for (;;) {
+               length += strcspn(p + length, reject);
+               c = p[length];
+               if (c && (!(c & 0x80)
+#if ENABLE_ASH_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_ASH_MATH_SUPPORT
+               case CTLENDARI:
+                       p--;
+                       expari(quotes);
+                       goto start;
+#endif
+               }
+       }
+ breakloop:
+       ;
+}
+
+static char *
+scanleft(char *startp, char *rmesc, char *rmescend ATTRIBUTE_UNUSED, char *str, int quotes,
+       int zero)
+{
+       char *loc;
+       char *loc2;
+       char c;
+
+       loc = startp;
+       loc2 = rmesc;
+       do {
+               int match;
+               const char *s = loc2;
+               c = *loc2;
+               if (zero) {
+                       *loc2 = '\0';
+                       s = rmesc;
+               }
+               match = pmatch(str, s);
+               *loc2 = c;
+               if (match)
+                       return loc;
+               if (quotes && *loc == CTLESC)
+                       loc++;
+               loc++;
+               loc2++;
+       } while (c);
+       return 0;
+}
+
+static char *
+scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes,
+       int zero)
+{
+       int esc = 0;
+       char *loc;
+       char *loc2;
+
+       for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
+               int match;
+               char c = *loc2;
+               const char *s = loc2;
+               if (zero) {
+                       *loc2 = '\0';
+                       s = rmesc;
+               }
+               match = pmatch(str, s);
+               *loc2 = c;
+               if (match)
+                       return loc;
+               loc--;
+               if (quotes) {
+                       if (--esc < 0) {
+                               esc = esclen(startp, loc);
+                       }
+                       if (esc % 2) {
+                               esc--;
+                               loc--;
+                       }
+               }
+       }
+       return 0;
+}
+
+static void varunset(const char *, const char *, const char *, int) ATTRIBUTE_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);
+}
+
+static const char *
+subevalvar(char *p, char *str, int strloc, int subtype,
+               int startloc, int varflags, int quotes, struct strlist *var_str_list)
+{
+       char *startp;
+       char *loc;
+       int saveherefd = herefd;
+       struct nodelist *saveargbackq = argbackq;
+       int amount;
+       char *rmesc, *rmescend;
+       int zero;
+       char *(*scan)(char *, char *, char *, char *, int , int);
+
+       herefd = -1;
+       argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0,
+                       var_str_list);
+       STPUTC('\0', expdest);
+       herefd = saveherefd;
+       argbackq = saveargbackq;
+       startp = stackblock() + startloc;
+
+       switch (subtype) {
+       case VSASSIGN:
+               setvar(str, startp, 0);
+               amount = startp - expdest;
+               STADJUST(amount, expdest);
+               return startp;
+
+       case VSQUESTION:
+               varunset(p, str, startp, varflags);
+               /* NOTREACHED */
+       }
+
+       subtype -= VSTRIMRIGHT;
+#if DEBUG
+       if (subtype < 0 || subtype > 3)
+               abort();
+#endif
+
+       rmesc = startp;
+       rmescend = stackblock() + strloc;
+       if (quotes) {
+               rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
+               if (rmesc != startp) {
+                       rmescend = expdest;
+                       startp = stackblock() + startloc;
+               }
+       }
+       rmescend--;
+       str = stackblock() + strloc;
+       preglob(str, varflags & VSQUOTE, 0);
+
+       /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
+       zero = subtype >> 1;
+       /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
+       scan = (subtype & 1) ^ zero ? scanleft : scanright;
+
+       loc = scan(startp, rmesc, rmescend, str, quotes, zero);
+       if (loc) {
+               if (zero) {
+                       memmove(startp, loc, str - loc);
+                       loc = startp + (str - loc) - 1;
+               }
+               *loc = '\0';
+               amount = loc - expdest;
+               STADJUST(amount, expdest);
+       }
+       return loc;
+}
+
+/*
+ * 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;
+       char *p;
+       int i;
+       int sep = 0;
+       int sepq = 0;
+       ssize_t len = 0;
+       char **ap;
+       int syntax;
+       int quoted = varflags & VSQUOTE;
+       int subtype = varflags & VSTYPE;
+       int quotes = flags & (EXP_FULL | EXP_CASE);
+
+       if (quoted && (flags & EXP_FULL))
+               sep = 1 << CHAR_BIT;
+
+       syntax = quoted ? DQSYNTAX : BASESYNTAX;
+       switch (*name) {
+       case '$':
+               num = rootpid;
+               goto numvar;
+       case '?':
+               num = exitstatus;
+               goto numvar;
+       case '#':
+               num = shellparam.nparam;
+               goto numvar;
+       case '!':
+               num = backgndpid;
+               if (num == 0)
+                       return -1;
+ numvar:
+               len = cvtnum(num);
+               break;
+       case '-':
+               p = makestrspace(NOPTS, expdest);
+               for (i = NOPTS - 1; i >= 0; i--) {
+                       if (optlist[i]) {
+                               USTPUTC(optletters(i), p);
+                               len++;
+                       }
+               }
+               expdest = p;
+               break;
+       case '@':
+               if (sep)
+                       goto param;
+               /* fall through */
+       case '*':
+               sep = ifsset() ? 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':
+               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 == (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:
+               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 {
+       int cmdtype;
+       union param {
+               int index;
+               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.
+ */
+
+#define ARB 1                   /* actual size determined at run time */
+
+struct tblentry {
+       struct tblentry *next;  /* next entry in hash chain */
+       union param param;      /* definition of builtin function */
+       short cmdtype;          /* index identifying command */
+       char rehash;            /* if set, cd done since entry created */
+       char cmdname[ARB];      /* name of command */
+};
+
+static struct tblentry **cmdtable;
+#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(char *cmd, char **argv, char **envp)
+{
+       int repeated = 0;
+
+#if ENABLE_FEATURE_SH_STANDALONE
+       if (strchr(cmd, '/') == NULL) {
+               int a = find_applet_by_name(cmd);
+               if (a >= 0) {
+                       if (APPLET_IS_NOEXEC(a))
+                               run_applet_no_and_exit(a, 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);
+       } else if (errno == ENOEXEC) {
+               char **ap;
+               char **new;
+
+               for (ap = argv; *ap; ap++)
+                       ;
+               ap = new = ckmalloc((ap - argv + 2) * sizeof(char *));
+               ap[1] = cmd;
+               ap[0] = cmd = (char *)DEFAULT_SHELL;
+               ap += 2;
+               argv++;
+               while ((*ap++ = *argv++))
+                       continue;
+               argv = new;
+               goto repeat;
+       }
+}
+
+/*
+ * Exec a program.  Never returns.  If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+#define environment() listvars(VEXPORT, VUNSET, 0)
+static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN;
+static void
+shellexec(char **argv, const char *path, int idx)
+{
+       char *cmdname;
+       int e;
+       char **envp;
+       int exerrno;
+
+       clearredir(1);
+       envp = environment();
+       if (strchr(argv[0], '/')
+#if ENABLE_FEATURE_SH_STANDALONE
+        || find_applet_by_name(argv[0]) >= 0
+#endif
+       ) {
+               tryexec(argv[0], argv, envp);
+               e = errno;
+       } else {
+               e = ENOENT;
+               while ((cmdname = padvance(&path, argv[0])) != NULL) {
+                       if (--idx < 0 && pathopt == NULL) {
+                               tryexec(cmdname, argv, envp);
+                               if (errno != ENOENT && errno != ENOTDIR)
+                                       e = errno;
+                       }
+                       stunalloc(cmdname);
+               }
+       }
+
+       /* Map to POSIX errors */
+       switch (e) {
+       case EACCES:
+               exerrno = 126;
+               break;
+       case ENOENT:
+               exerrno = 127;
+               break;
+       default:
+               exerrno = 2;
+               break;
+       }
+       exitstatus = exerrno;
+       TRACE(("shellexec failed for %s, errno %d, suppressint %d\n",
+               argv[0], e, suppressint ));
+       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) - ARB
+                                       + strlen(name) + 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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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
+
+/* 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 == -1) {
+                       p = command;
+               } else {
+                       do {
+                               p = padvance(&path, command);
+                               stunalloc(p);
+                       } while (--j >= 0);
+               }
+               if (describe_command_verbose) {
+                       out1fmt(" is%s %s",
+                               (cmdp ? " a tracked alias for" : nullstr), p
+                       );
+               } else {
+                       out1str(p);
+               }
+               break;
+       }
+
+       case CMDFUNCTION:
+               if (describe_command_verbose) {
+                       out1str(" is a shell function");
+               } else {
+                       out1str(command);
+               }
+               break;
+
+       case CMDBUILTIN:
+               if (describe_command_verbose) {
+                       out1fmt(" is a %sshell builtin",
+                               IS_BUILTIN_SPECIAL(entry.u.cmd) ?
+                                       "special " : nullstr
+                       );
+               } else {
+                       out1str(command);
+               }
+               break;
+
+       default:
+               if (describe_command_verbose) {
+                       out1str(": not found\n");
+               }
+               return 127;
+       }
+ out:
+       outstr("\n", stdout);
+       return 0;
+}
+
+static int
+typecmd(int argc ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       int c;
+       enum {
+               VERIFY_BRIEF = 1,
+               VERIFY_VERBOSE = 2,
+       } verify = 0;
+
+       while ((c = nextopt("pvV")) != '\0')
+               if (c == 'V')
+                       verify |= VERIFY_VERBOSE;
+               else if (c == 'v')
+                       verify |= VERIFY_BRIEF;
+#if DEBUG
+               else if (c != 'p')
+                       abort();
+#endif
+       if (verify)
+               return describe_command(*argptr, verify - VERIFY_BRIEF);
+
+       return 0;
+}
+#endif
+
+
+/* ============ 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[26] = {
+       SHELL_ALIGN(sizeof(struct ncmd)),
+       SHELL_ALIGN(sizeof(struct npipe)),
+       SHELL_ALIGN(sizeof(struct nredir)),
+       SHELL_ALIGN(sizeof(struct nredir)),
+       SHELL_ALIGN(sizeof(struct nredir)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nif)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nfor)),
+       SHELL_ALIGN(sizeof(struct ncase)),
+       SHELL_ALIGN(sizeof(struct nclist)),
+       SHELL_ALIGN(sizeof(struct narg)),
+       SHELL_ALIGN(sizeof(struct narg)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct ndup)),
+       SHELL_ALIGN(sizeof(struct ndup)),
+       SHELL_ALIGN(sizeof(struct nhere)),
+       SHELL_ALIGN(sizeof(struct nhere)),
+       SHELL_ALIGN(sizeof(struct nnot)),
+};
+
+static void calcsize(union node *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:
+       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.backgnd = n->npipe.backgnd;
+               break;
+       case NREDIR:
+       case NBACKGND:
+       case NSUBSHELL:
+               new->nredir.redirect = copynode(n->nredir.redirect);
+               new->nredir.n = copynode(n->nredir.n);
+               break;
+       case NAND:
+       case NOR:
+       case NSEMI:
+       case NWHILE:
+       case NUNTIL:
+               new->nbinary.ch2 = copynode(n->nbinary.ch2);
+               new->nbinary.ch1 = copynode(n->nbinary.ch1);
+               break;
+       case NIF:
+               new->nif.elsepart = copynode(n->nif.elsepart);
+               new->nif.ifpart = copynode(n->nif.ifpart);
+               new->nif.test = copynode(n->nif.test);
+               break;
+       case NFOR:
+               new->nfor.var = 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:
+       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;
+}
+
+static int evalskip;            /* set if we are skipping commands */
+/* 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 int skipcount;           /* number of levels to skip */
+static int funcnest;            /* depth of function calls */
+
+/* forward decl way out to parsing code - dotrap needs it */
+static int evalstring(char *s, int mask);
+
+/*
+ * Called to execute a trap.  Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
+ */
+static int
+dotrap(void)
+{
+       char *p;
+       char *q;
+       int i;
+       int savestatus;
+       int skip;
+
+       savestatus = exitstatus;
+       pendingsig = 0;
+       xbarrier();
+
+       for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) {
+               if (!*q)
+                       continue;
+               *q = '\0';
+
+               p = trap[i + 1];
+               if (!p)
+                       continue;
+               skip = evalstring(p, SKIPEVAL);
+               exitstatus = savestatus;
+               if (skip)
+                       return skip;
+       }
+
+       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)
+{
+       int checkexit = 0;
+       void (*evalfn)(union node *, int);
+       unsigned isor;
+       int status;
+       if (n == NULL) {
+               TRACE(("evaltree(NULL) called\n"));
+               goto out;
+       }
+       TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
+                       getpid(), n, n->type, flags));
+       switch (n->type) {
+       default:
+#if DEBUG
+               out1fmt("Node type = %d\n", n->type);
+               fflush(stdout);
+               break;
+#endif
+       case NNOT:
+               evaltree(n->nnot.com, EV_TESTED);
+               status = !exitstatus;
+               goto setstatus;
+       case NREDIR:
+               expredir(n->nredir.redirect);
+               status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
+               if (!status) {
+                       evaltree(n->nredir.n, flags & EV_TESTED);
+                       status = exitstatus;
+               }
+               popredir(0);
+               goto setstatus;
+       case NCMD:
+               evalfn = evalcommand;
+ checkexit:
+               if (eflag && !(flags & EV_TESTED))
+                       checkexit = ~0;
+               goto calleval;
+       case NFOR:
+               evalfn = evalfor;
+               goto calleval;
+       case NWHILE:
+       case NUNTIL:
+               evalfn = evalloop;
+               goto calleval;
+       case NSUBSHELL:
+       case NBACKGND:
+               evalfn = evalsubshell;
+               goto calleval;
+       case NPIPE:
+               evalfn = evalpipe;
+               goto checkexit;
+       case NCASE:
+               evalfn = evalcase;
+               goto calleval;
+       case NAND:
+       case NOR:
+       case NSEMI:
+#if NAND + 1 != NOR
+#error NAND + 1 != NOR
+#endif
+#if NOR + 1 != NSEMI
+#error NOR + 1 != NSEMI
+#endif
+               isor = n->type - NAND;
+               evaltree(
+                       n->nbinary.ch1,
+                       (flags | ((isor >> 1) - 1)) & EV_TESTED
+               );
+               if (!exitstatus == isor)
+                       break;
+               if (!evalskip) {
+                       n = n->nbinary.ch2;
+ evaln:
+                       evalfn = evaltree;
+ calleval:
+                       evalfn(n, flags);
+                       break;
+               }
+               break;
+       case NIF:
+               evaltree(n->nif.test, EV_TESTED);
+               if (evalskip)
+                       break;
+               if (exitstatus == 0) {
+                       n = n->nif.ifpart;
+                       goto evaln;
+               } else if (n->nif.elsepart) {
+                       n = n->nif.elsepart;
+                       goto evaln;
+               }
+               goto success;
+       case NDEFUN:
+               defun(n->narg.text, n->narg.next);
+ success:
+               status = 0;
+ setstatus:
+               exitstatus = status;
+               break;
+       }
+ out:
+       if ((checkexit & exitstatus))
+               evalskip |= SKIPEVAL;
+       else if (pendingsig && dotrap())
+               goto exexit;
+
+       if (flags & EV_EXIT) {
+ exexit:
+               raise_exception(EXEXIT);
+       }
+}
+
+#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
+static
+#endif
+void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
+
+static int loopnest;            /* current loop nesting level */
+
+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:
+               case NCLOBBER:
+               case NAPPEND:
+                       expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+                       redir->nfile.expfname = fn.list->text;
+                       break;
+               case NFROMFD:
+               case NTOFD:
+                       if (redir->ndup.vname) {
+                               expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+                               if (fn.list == NULL)
+                                       ash_msg_and_raise_error("redir error");
+                               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.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];
+               close(pip[1]);
+       }
+       if (n->npipe.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 int 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
+}
+
+#if ENABLE_FEATURE_EDITING_VI
+#define setvimode(on) do { \
+       if (on) line_input_state->flags |= VI_MODE; \
+       else line_input_state->flags &= ~VI_MODE; \
+} while (0)
+#else
+#define setvimode(on) viflag = 0   /* forcibly keep the option off */
+#endif
+
+static void
+optschanged(void)
+{
+#if DEBUG
+       opentrace();
+#endif
+       setinteractive(iflag);
+       setjobctl(mflag);
+       setvimode(viflag);
+}
+
+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 ATTRIBUTE_UNUSED, char **argv)
+{
+       char *name;
+
+       argv = argptr;
+       while ((name = *argv++) != NULL) {
+               mklocal(name);
+       }
+       return 0;
+}
+
+static int
+falsecmd(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       return 1;
+}
+
+static int
+truecmd(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       return 0;
+}
+
+static int
+execcmd(int argc ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 **);
+#if ENABLE_ASH_BUILTIN_ECHO
+static int echocmd(int, char **);
+#endif
+#if ENABLE_ASH_BUILTIN_TEST
+static int testcmd(int, char **);
+#endif
+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 argc, char **argv);
+#endif
+#if ENABLE_ASH_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"
+
+/* make sure to 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 },
+       { BUILTIN_REGULAR       "[[", testcmd },
+#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_ASH_MATH_SUPPORT
+       { BUILTIN_NOSPEC        "let", letcmd },
+#endif
+       { BUILTIN_ASSIGN        "local", localcmd },
+       { BUILTIN_NOSPEC        "pwd", pwdcmd },
+       { BUILTIN_REGULAR       "read", readcmd },
+       { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd },
+       { BUILTIN_SPEC_REG      "return", returncmd },
+       { BUILTIN_SPEC_REG      "set", setcmd },
+       { BUILTIN_SPEC_REG      "shift", shiftcmd },
+       { BUILTIN_SPEC_REG      "source", dotcmd },
+#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 },
+};
+
+
+#define COMMANDCMD (builtintab + 5 + \
+       2 * ENABLE_ASH_BUILTIN_TEST + \
+       ENABLE_ASH_ALIAS + \
+       ENABLE_ASH_JOB_CONTROL)
+#define EXECCMD (builtintab + 7 + \
+       2 * ENABLE_ASH_BUILTIN_TEST + \
+       ENABLE_ASH_ALIAS + \
+       ENABLE_ASH_JOB_CONTROL + \
+       ENABLE_ASH_CMDCMD + \
+       ENABLE_ASH_BUILTIN_ECHO)
+
+/*
+ * 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 back_exitstatus; /* exit status of backquoted command */
+static int
+isassignment(const char *p)
+{
+       const char *q = endofname(p);
+       if (p == q)
+               return 0;
+       return *q == '=';
+}
+static int
+bltincmd(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       /* 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 cmd_is_exec;
+       int status;
+       char **nargv;
+       struct builtincmd *bcmd;
+       int pseudovarflag = 0;
+
+       /* First expand the arguments. */
+       TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
+       setstackmark(&smark);
+       back_exitstatus = 0;
+
+       cmdentry.cmdtype = CMDBUILTIN;
+       cmdentry.u.cmd = &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) {
+                               status = 127;
+                               flush_stderr();
+                               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++;
+#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:
+               /* 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;
+                               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);
+               }
+               if (evalbltin(cmdentry.u.cmd, argc, argv)) {
+                       int exit_status;
+                       int i = exception;
+                       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);
+               if (evalfun(cmdentry.u.func, argc, argv, flags))
+                       goto raise;
+               break;
+       }
+
+ out:
+       popredir(cmd_is_exec);
+       if (lastarg)
+               /* dsl: I think this is intended to be used to support
+                * '_' in 'vi' command mode during line editing...
+                * However I implemented that within libedit itself.
+                */
+               setvar("_", lastarg, 0);
+       popstackmark(&smark);
+}
+
+static int
+evalbltin(const struct builtincmd *cmd, int argc, char **argv)
+{
+       char *volatile savecmdname;
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+       int i;
+
+       savecmdname = commandname;
+       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 ATTRIBUTE_UNUSED, 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.
+ */
+
+#define EOF_NLEFT -99           /* value of parsenleft when EOF pushed back */
+
+enum {
+       INPUT_PUSH_FILE = 1,
+       INPUT_NOFILE_OK = 2,
+};
+
+static int plinno = 1;                  /* input line number */
+/* number of characters left in input buffer */
+static int parsenleft;                  /* copy of parsefile->nleft */
+static int parselleft;                  /* copy of parsefile->lleft */
+/* next character in input buffer */
+static char *parsenextc;                /* copy of parsefile->nextc */
+
+static int checkkwd;
+/* values of checkkwd variable */
+#define CHKALIAS        0x1
+#define CHKKWD          0x2
+#define CHKNL           0x4
+
+static void
+popstring(void)
+{
+       struct strpush *sp = parsefile->strpush;
+
+       INT_OFF;
+#if ENABLE_ASH_ALIAS
+       if (sp->ap) {
+               if (parsenextc[-1] == ' ' || parsenextc[-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
+       parsenextc = sp->prevstring;
+       parsenleft = sp->prevnleft;
+/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
+       parsefile->strpush = sp->prev;
+       if (sp != &(parsefile->basestrpush))
+               free(sp);
+       INT_ON;
+}
+
+static int
+preadfd(void)
+{
+       int nr;
+       char *buf =  parsefile->buf;
+       parsenextc = buf;
+
+#if ENABLE_FEATURE_EDITING
+ retry:
+       if (!iflag || parsefile->fd)
+               nr = nonblock_safe_read(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(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 (parsenleft == EOF_NLEFT) or we are reading
+ *    from a string so we can't refill the buffer, return EOF.
+ * 3) If the is more stuff in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
+static int
+preadbuffer(void)
+{
+       char *q;
+       int more;
+       char savec;
+
+       while (parsefile->strpush) {
+#if ENABLE_ASH_ALIAS
+               if (parsenleft == -1 && parsefile->strpush->ap &&
+                       parsenextc[-1] != ' ' && parsenextc[-1] != '\t') {
+                       return PEOA;
+               }
+#endif
+               popstring();
+               if (--parsenleft >= 0)
+                       return signed_char2int(*parsenextc++);
+       }
+       if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+               return PEOF;
+       flush_stdout_stderr();
+
+       more = parselleft;
+       if (more <= 0) {
+ again:
+               more = preadfd();
+               if (more <= 0) {
+                       parselleft = parsenleft = EOF_NLEFT;
+                       return PEOF;
+               }
+       }
+
+       q = parsenextc;
+
+       /* delete nul characters */
+       for (;;) {
+               int c;
+
+               more--;
+               c = *q;
+
+               if (!c)
+                       memmove(q, q + 1, more);
+               else {
+                       q++;
+                       if (c == '\n') {
+                               parsenleft = q - parsenextc - 1;
+                               break;
+                       }
+               }
+
+               if (more <= 0) {
+                       parsenleft = q - parsenextc - 1;
+                       if (parsenleft < 0)
+                               goto again;
+                       break;
+               }
+       }
+       parselleft = more;
+
+       savec = *q;
+       *q = '\0';
+
+       if (vflag) {
+               out2str(parsenextc);
+       }
+
+       *q = savec;
+
+       return signed_char2int(*parsenextc++);
+}
+
+#define pgetc_as_macro() (--parsenleft >= 0? signed_char2int(*parsenextc++) : preadbuffer())
+static int
+pgetc(void)
+{
+       return pgetc_as_macro();
+}
+
+#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
+#define pgetc_macro() pgetc()
+#else
+#define pgetc_macro() pgetc_as_macro()
+#endif
+
+/*
+ * Same as pgetc(), but ignores PEOA.
+ */
+#if ENABLE_ASH_ALIAS
+static int
+pgetc2(void)
+{
+       int c;
+
+       do {
+               c = pgetc_macro();
+       } while (c == PEOA);
+       return c;
+}
+#else
+static int
+pgetc2(void)
+{
+       return pgetc_macro();
+}
+#endif
+
+/*
+ * Read a line from the script.
+ */
+static char *
+pfgets(char *line, int len)
+{
+       char *p = line;
+       int nleft = len;
+       int c;
+
+       while (--nleft > 0) {
+               c = pgetc2();
+               if (c == PEOF) {
+                       if (p == line)
+                               return NULL;
+                       break;
+               }
+               *p++ = c;
+               if (c == '\n')
+                       break;
+       }
+       *p = '\0';
+       return line;
+}
+
+/*
+ * Undo the last call to pgetc.  Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
+static void
+pungetc(void)
+{
+       parsenleft++;
+       parsenextc--;
+}
+
+/*
+ * 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;
+       size_t len;
+
+       len = strlen(s);
+       INT_OFF;
+/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
+       if (parsefile->strpush) {
+               sp = ckzalloc(sizeof(struct strpush));
+               sp->prev = parsefile->strpush;
+               parsefile->strpush = sp;
+       } else
+               sp = parsefile->strpush = &(parsefile->basestrpush);
+       sp->prevstring = parsenextc;
+       sp->prevnleft = parsenleft;
+#if ENABLE_ASH_ALIAS
+       sp->ap = ap;
+       if (ap) {
+               ap->flag |= ALIASINUSE;
+               sp->string = s;
+       }
+#endif
+       parsenextc = s;
+       parsenleft = len;
+       INT_ON;
+}
+
+/*
+ * To handle the "." command, a stack of input files is used.  Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+static void
+pushfile(void)
+{
+       struct parsefile *pf;
+
+       parsefile->nleft = parsenleft;
+       parsefile->lleft = parselleft;
+       parsefile->nextc = parsenextc;
+       parsefile->linno = plinno;
+       pf = ckzalloc(sizeof(*pf));
+       pf->prev = parsefile;
+       pf->fd = -1;
+       /*pf->strpush = NULL; - ckzalloc did it */
+       /*pf->basestrpush.prev = NULL;*/
+       parsefile = pf;
+}
+
+static void
+popfile(void)
+{
+       struct parsefile *pf = parsefile;
+
+       INT_OFF;
+       if (pf->fd >= 0)
+               close(pf->fd);
+       free(pf->buf);
+       while (pf->strpush)
+               popstring();
+       parsefile = pf->prev;
+       free(pf);
+       parsenleft = parsefile->nleft;
+       parselleft = parsefile->lleft;
+       parsenextc = parsefile->nextc;
+       plinno = parsefile->linno;
+       INT_ON;
+}
+
+/*
+ * Return to top level.
+ */
+static void
+popallfiles(void)
+{
+       while (parsefile != &basepf)
+               popfile();
+}
+
+/*
+ * Close the file(s) that the shell is reading commands from.  Called
+ * after a fork is done.
+ */
+static void
+closescript(void)
+{
+       popallfiles();
+       if (parsefile->fd > 0) {
+               close(parsefile->fd);
+               parsefile->fd = 0;
+       }
+}
+
+/*
+ * 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();
+               parsefile->buf = 0;
+       }
+       parsefile->fd = fd;
+       if (parsefile->buf == NULL)
+               parsefile->buf = ckmalloc(IBUFSIZ);
+       parselleft = parsenleft = 0;
+       plinno = 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();
+       parsenextc = string;
+       parsenleft = strlen(string);
+       parsefile->buf = NULL;
+       plinno = 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++);
+#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 ATTRIBUTE_UNUSED)
+{
+       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++);
+       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
+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 -o %s", name);
+               return 1;
+       }
+       out1str("Current option settings\n");
+       for (i = 0; i < NOPTS; i++)
+               out1fmt("%-16s%s\n", optnames(i),
+                               optlist[i] ? "on" : "off");
+       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", 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 (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 ATTRIBUTE_UNUSED, char **argv)
+{
+       int n;
+       char **ap1, **ap2;
+
+       n = 1;
+       if (argv[1])
+               n = number(argv[1]);
+       if (n > shellparam.nparam)
+               ash_msg_and_raise_error("can't shift that many");
+       INT_OFF;
+       shellparam.nparam -= n;
+       for (ap1 = shellparam.p; --n >= 0; ap1++) {
+               if (shellparam.malloced)
+                       free(*ap1);
+       }
+       ap2 = shellparam.p;
+       while ((*ap2++ = *ap1++) != NULL);
+#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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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
+/* Roughly copied from bash.. */
+static void
+change_random(const char *value)
+{
+       if (value == NULL) {
+               /* "get", generate */
+               char buf[16];
+
+               rseed = rseed * 1103515245 + 12345;
+               sprintf(buf, "%d", (unsigned int)((rseed & 32767)));
+               /* set without recursion */
+               setvar(vrandom.text, buf, VNOFUNC);
+               vrandom.flags &= ~VNOFUNC;
+       } else {
+               /* set/reset */
+               rseed = strtoul(value, (char **)NULL, 10);
+       }
+}
+#endif
+
+#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 || 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 */
+
+/*
+ * NEOF is returned by parsecmd when it encounters an end of file.  It
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
+ */
+static smallint tokpushback;           /* last token pushed back */
+#define NEOF ((union node *)&tokpushback)
+static smallint parsebackquote;        /* nonzero if we are inside backquotes */
+static int lasttoken;                  /* last token read */
+static char *wordtext;                 /* text of last word returned by readtoken */
+static struct nodelist *backquotelist;
+static union node *redirnode;
+static struct heredoc *heredoc;
+static smallint quoteflag;             /* set if (part of) last token was quoted */
+
+static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN;
+static void
+raise_error_syntax(const char *msg)
+{
+       ash_msg_and_raise_error("syntax error: %s", msg);
+       /* NOTREACHED */
+}
+
+/*
+ * 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) ATTRIBUTE_NORETURN;
+static void
+raise_error_unexpected_syntax(int token)
+{
+       char msg[64];
+       int l;
+
+       l = sprintf(msg, "%s unexpected", tokname(lasttoken));
+       if (token >= 0)
+               sprintf(msg + l, " (expecting %s)", tokname(token));
+       raise_error_syntax(msg);
+       /* NOTREACHED */
+}
+
+#define EOFMARKLEN 79
+
+struct heredoc {
+       struct heredoc *next;   /* next here document in list */
+       union node *here;       /* redirection node */
+       char *eofmark;          /* string indicating end of input */
+       int striptabs;          /* if set, strip leading tabs */
+};
+
+static struct heredoc *heredoclist;    /* list of here documents to read */
+
+/* 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.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.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)
+{
+       TRACE(("Fix redir %s %d\n", text, err));
+       if (!err)
+               n->ndup.vname = NULL;
+
+       if (isdigit(text[0]) && text[1] == '\0')
+               n->ndup.dupfd = text[0] - '0';
+       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(char *text)
+{
+       char *p;
+       char c;
+
+       p = text;
+       while ((c = *p++) != '\0') {
+               if (c == CTLQUOTEMARK)
+                       continue;
+               if (c == CTLESC)
+                       p++;
+               else if (SIT(c, BASESYNTAX) == CCTL)
+                       return 0;
+       }
+       return 1;
+}
+
+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;
+
+       args = NULL;
+       app = &args;
+       vars = NULL;
+       vpp = &vars;
+       redir = NULL;
+       rpp = &redir;
+
+       savecheckkwd = CHKALIAS;
+       for (;;) {
+               checkkwd = savecheckkwd;
+               switch (readtoken()) {
+               case TWORD:
+                       n = stzalloc(sizeof(struct narg));
+                       n->type = NARG;
+                       /*n->narg.next = NULL; - stzalloc did it */
+                       n->narg.text = wordtext;
+                       n->narg.backquote = backquotelist;
+                       if (savecheckkwd && isassignment(wordtext)) {
+                               *vpp = n;
+                               vpp = &n->narg.next;
+                       } else {
+                               *app = n;
+                               app = &n->narg.next;
+                               savecheckkwd = 0;
+                       }
+                       break;
+               case TREDIR:
+                       *rpp = n = redirnode;
+                       rpp = &n->nfile.next;
+                       parsefname();   /* read name of redirection file */
+                       break;
+               case TLP:
+                       if (args && app == &args->narg.next
+                        && !vars && !redir
+                       ) {
+                               struct builtincmd *bcmd;
+                               const char *name;
+
+                               /* We have a function */
+                               if (readtoken() != TRP)
+                                       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 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 */
+
+#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 = plinno;
+       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);
+                               plinno++;
+                               if (doprompt)
+                                       setprompt(2);
+                               c = pgetc();
+                               goto loop;              /* continue outer loop */
+                       case CWORD:
+                               USTPUTC(c, out);
+                               break;
+                       case CCTL:
+                               if (eofmark == NULL || dblquote)
+                                       USTPUTC(CTLESC, out);
+                               USTPUTC(c, out);
+                               break;
+                       case CBACK:     /* backslash */
+                               c = pgetc2();
+                               if (c == PEOF) {
+                                       USTPUTC(CTLESC, out);
+                                       USTPUTC('\\', out);
+                                       pungetc();
+                               } else if (c == '\n') {
+                                       if (doprompt)
+                                               setprompt(2);
+                               } else {
+#if 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:
+                               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_ASH_MATH_SUPPORT
+                       case CLP:       /* '(' in arithmetic */
+                               parenlevel++;
+                               USTPUTC(c, out);
+                               break;
+                       case CRP:       /* ')' in arithmetic */
+                               if (parenlevel > 0) {
+                                       USTPUTC(c, out);
+                                       --parenlevel;
+                               } else {
+                                       if (pgetc() == ')') {
+                                               if (--arinest == 0) {
+                                                       USTPUTC(CTLENDARI, out);
+                                                       syntax = prevsyntax;
+                                                       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)
+                                       goto endword;   /* exit outer loop */
+#if ENABLE_ASH_ALIAS
+                               if (c != PEOA)
+#endif
+                                       USTPUTC(c, out);
+
+                       }
+                       c = pgetc_macro();
+               }
+       }
+ endword:
+#if ENABLE_ASH_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 = plinno;
+               /* { */
+               raise_error_syntax("Missing '}'");
+       }
+       USTPUTC('\0', out);
+       len = out - (char *)stackblock();
+       out = stackblock();
+       if (eofmark == NULL) {
+               if ((c == '>' || c == '<')
+                && quotef == 0
+                && len <= 2
+                && (*out == '\0' || isdigit(*out))) {
+                       PARSEREDIR();
+                       return lasttoken = TREDIR;
+               } else {
+                       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++);
+                               if (*p == '\n' && *q == '\0') {
+                                       c = PEOF;
+                                       plinno++;
+                                       needprompt = doprompt;
+                               } else {
+                                       pushstring(line, NULL);
+                               }
+                       }
+               }
+       }
+       goto checkend_return;
+}
+
+/*
+ * Parse a redirection operator.  The variable "out" points to a string
+ * specifying the fd to be redirected.  The variable "c" contains the
+ * first character of the redirection operator.
+ */
+parseredir: {
+       char fd = *out;
+       union node *np;
+
+       np = 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;
+               else {
+                       np->type = NTO;
+                       pungetc();
+               }
+       } 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 - '0';
+       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 int)c) - 33 < 32) \
+                       && ((0xc1ff920dUL >> (((unsigned int)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))
+       ) {
+               USTPUTC('$', out);
+               pungetc();
+       } else if (c == '(') {  /* $(command) or $((arith)) */
+               if (pgetc() == '(') {
+#if ENABLE_ASH_MATH_SUPPORT
+                       PARSEARITH();
+#else
+                       raise_error_syntax("We unsupport $((arith))");
+#endif
+               } else {
+                       pungetc();
+                       PARSEBACKQNEW();
+               }
+       } else {
+               USTPUTC(CTLVAR, out);
+               typeloc = out - (char *)stackblock();
+               USTPUTC(VSNORMAL, out);
+               subtype = VSNORMAL;
+               if (c == '{') {
+                       c = pgetc();
+                       if (c == '#') {
+                               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 ':':
+                               flags = VSNUL;
+                               c = pgetc();
+                               /*FALLTHROUGH*/
+                       default:
+                               p = strchr(types, c);
+                               if (p == NULL)
+                                       goto badsub;
+                               subtype = p - types + VSNORMAL;
+                               break;
+                       case '%':
+                       case '#':
+                               {
+                                       int cc = c;
+                                       subtype = c == '#' ? VSTRIMLEFT :
+                                                            VSTRIMRIGHT;
+                                       c = pgetc();
+                                       if (c == cc)
+                                               subtype++;
+                                       else
+                                               pungetc();
+                                       break;
+                               }
+                       }
+               } else {
+                       pungetc();
+               }
+               if (dblquote || arinest)
+                       flags |= VSQUOTE;
+               *((char *)stackblock() + typeloc) = subtype | flags;
+               if (subtype != VSNORMAL) {
+                       varnest++;
+                       if (dblquote || arinest) {
+                               dqvarnest++;
+                       }
+               }
+       }
+       goto parsesub_return;
+}
+
+/*
+ * Called to parse command substitutions.  Newstyle is set if the command
+ * is enclosed inside $(...); nlpp is a pointer to the head of the linked
+ * list of commands (passed by reference), and savelen is the number of
+ * characters on the top of the stack which must be preserved.
+ */
+parsebackq: {
+       struct nodelist **nlpp;
+       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') {
+                                       plinno++;
+                                       if (doprompt)
+                                               setprompt(2);
+                                       /*
+                                        * If eating a newline, avoid putting
+                                        * the newline into the new character
+                                        * stream (via the STPUTC after the
+                                        * switch).
+                                        */
+                                       continue;
+                               }
+                               if (pc != '\\' && pc != '`' && pc != '$'
+                                && (!dblquote || pc != '"'))
+                                       STPUTC('\\', pout);
+                               if (pc > PEOA_OR_PEOF) {
+                                       break;
+                               }
+                               /* fall through */
+
+                       case PEOF:
+#if ENABLE_ASH_ALIAS
+                       case PEOA:
+#endif
+                               startlinno = plinno;
+                               raise_error_syntax("EOF in backquote substitution");
+
+                       case '\n':
+                               plinno++;
+                               needprompt = doprompt;
+                               break;
+
+                       default:
+                               break;
+                       }
+                       STPUTC(pc, pout);
+               }
+ done:
+               STPUTC('\0', pout);
+               psavelen = pout - (char *)stackblock();
+               if (psavelen > 0) {
+                       pstr = grabstackstr(pout);
+                       setinputstring(pstr);
+               }
+       }
+       nlpp = &bqlist;
+       while (*nlpp)
+               nlpp = &(*nlpp)->next;
+       *nlpp = 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_ASH_MATH_SUPPORT
+/*
+ * Parse an arithmetic expansion (indicate start of one and set state)
+ */
+parsearith: {
+       if (++arinest == 1) {
+               prevsyntax = syntax;
+               syntax = ARISYNTAX;
+               USTPUTC(CTLARI, out);
+               if (dblquote)
+                       USTPUTC('"', out);
+               else
+                       USTPUTC(' ', out);
+       } else {
+               /*
+                * we collapse embedded arithmetic expansion to
+                * parenthesis, which should be equivalent
+                */
+               USTPUTC('(', out);
+       }
+       goto parsearith_return;
+}
+#endif
+
+} /* end of readtoken */
+
+/*
+ * 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', '(', ')', '&', '|', ';', 0
+};
+
+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 */
+};
+
+#define xxreadtoken_doubles \
+       (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars))
+#define xxreadtoken_singles \
+       (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1)
+
+static int
+xxreadtoken(void)
+{
+       int c;
+
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+       }
+       startlinno = plinno;
+       for (;;) {                      /* until token or start of word found */
+               c = pgetc_macro();
+
+               if ((c != ' ') && (c != '\t')
+#if ENABLE_ASH_ALIAS
+                && (c != PEOA)
+#endif
+               ) {
+                       if (c == '#') {
+                               while ((c = pgetc()) != '\n' && c != PEOF);
+                               pungetc();
+                       } else if (c == '\\') {
+                               if (pgetc() != '\n') {
+                                       pungetc();
+                                       goto READTOKEN1;
+                               }
+                               startlinno = ++plinno;
+                               if (doprompt)
+                                       setprompt(2);
+                       } else {
+                               const char *p
+                                       = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1;
+
+                               if (c != PEOF) {
+                                       if (c == '\n') {
+                                               plinno++;
+                                               needprompt = doprompt;
+                                       }
+
+                                       p = strchr(xxreadtoken_chars, c);
+                                       if (p == NULL) {
+ READTOKEN1:
+                                               return readtoken1(c, BASESYNTAX, (char *) NULL, 0);
+                                       }
+
+                                       if (p - xxreadtoken_chars >= xxreadtoken_singles) {
+                                               if (pgetc() == *p) {    /* double occurrence? */
+                                                       p += xxreadtoken_doubles + 1;
+                                               } else {
+                                                       pungetc();
+                                               }
+                                       }
+                               }
+                               return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars];
+                       }
+               }
+       } /* for */
+}
+#else
+#define RETURN(token)   return lasttoken = token
+static int
+xxreadtoken(void)
+{
+       int c;
+
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+       }
+       startlinno = plinno;
+       for (;;) {      /* until token or start of word found */
+               c = pgetc_macro();
+               switch (c) {
+               case ' ': case '\t':
+#if ENABLE_ASH_ALIAS
+               case PEOA:
+#endif
+                       continue;
+               case '#':
+                       while ((c = pgetc()) != '\n' && c != PEOF);
+                       pungetc();
+                       continue;
+               case '\\':
+                       if (pgetc() == '\n') {
+                               startlinno = ++plinno;
+                               if (doprompt)
+                                       setprompt(2);
+                               continue;
+                       }
+                       pungetc();
+                       goto breakloop;
+               case '\n':
+                       plinno++;
+                       needprompt = doprompt;
+                       RETURN(TNL);
+               case PEOF:
+                       RETURN(TEOF);
+               case '&':
+                       if (pgetc() == '&')
+                               RETURN(TAND);
+                       pungetc();
+                       RETURN(TBACKGND);
+               case '|':
+                       if (pgetc() == '|')
+                               RETURN(TOR);
+                       pungetc();
+                       RETURN(TPIPE);
+               case ';':
+                       if (pgetc() == ';')
+                               RETURN(TENDCASE);
+                       pungetc();
+                       RETURN(TSEMI);
+               case '(':
+                       RETURN(TLP);
+               case ')':
+                       RETURN(TRP);
+               default:
+                       goto breakloop;
+               }
+       }
+ breakloop:
+       return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
+#undef RETURN
+}
+#endif /* NEW_xxreadtoken */
+
+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. */
+       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 ATTRIBUTE_UNUSED, 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 (jobctl)
+                       showjobs(stderr, SHOW_CHANGED);
+#endif
+               inter = 0;
+               if (iflag && top) {
+                       inter++;
+#if ENABLE_ASH_MAIL
+                       chkmail();
+#endif
+               }
+               n = parsecmd(inter);
+               /* showtree(n); DEBUG */
+               if (n == NEOF) {
+                       if (!top || numeof >= 50)
+                               break;
+                       if (!stoppedjobs()) {
+                               if (!Iflag)
+                                       break;
+                               out2str("\nUse \"exit\" to leave shell.\n");
+                       }
+                       numeof++;
+               } else if (nflag == 0) {
+                       /* job_warning 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;
+
+       while ((fullname = padvance(&path, name)) != NULL) {
+               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
+                       /*
+                        * Don't bother freeing here, since it will
+                        * be freed by the caller.
+                        */
+                       return fullname;
+               }
+               stunalloc(fullname);
+       }
+
+       /* not found in the PATH */
+       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 ATTRIBUTE_UNUSED, char **argv)
+{
+       if (stoppedjobs())
+               return 0;
+       if (argv[1])
+               exitstatus = number(argv[1]);
+       raise_exception(EXEXIT);
+       /* NOTREACHED */
+}
+
+#if ENABLE_ASH_BUILTIN_ECHO
+static int
+echocmd(int argc, char **argv)
+{
+       return echo_main(argc, argv);
+}
+#endif
+
+#if ENABLE_ASH_BUILTIN_TEST
+static int
+testcmd(int argc, char **argv)
+{
+       return test_main(argc, argv);
+}
+#endif
+
+/*
+ * 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
+       if (find_applet_by_name(name) >= 0) {
+               entry->cmdtype = CMDNORMAL;
+               entry->u.index = -1;
+               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;
+                       } else if (!(act & DO_NOFUNC)
+                        && prefix(pathopt, "func")) {
+                               /* handled below */
+                       } else {
+                               /* ignore unimplemented options */
+                               continue;
+                       }
+               }
+               /* if rehash, don't redo absolute path names */
+               if (fullname[0] == '/' && idx <= prev) {
+                       if (idx < prev)
+                               continue;
+                       TRACE(("searchexec \"%s\": no change\n", name));
+                       goto success;
+               }
+               while (stat(fullname, &statb) < 0) {
+#ifdef SYSV
+                       if (errno == EINTR)
+                               continue;
+#endif
+                       if (errno != ENOENT && errno != ENOTDIR)
+                               e = errno;
+                       goto loop;
+               }
+               e = EACCES;     /* if we fail, this will be the error */
+               if (!S_ISREG(statb.st_mode))
+                       continue;
+               if (pathopt) {          /* this is a %func directory */
+                       stalloc(strlen(fullname) + 1);
+                       /* 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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       char *action;
+       char **ap;
+       int signo;
+
+       nextopt(nullstr);
+       ap = argptr;
+       if (!*ap) {
+               for (signo = 0; signo < NSIG; signo++) {
+                       if (trap[signo] != NULL) {
+                               const char *sn;
+
+                               sn = get_signame(signo);
+                               out1fmt("trap -- %s %s\n",
+                                       single_quote(trap[signo]), sn);
+                       }
+               }
+               return 0;
+       }
+       if (!ap[1])
+               action = NULL;
+       else
+               action = *ap++;
+       while (*ap) {
+               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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       int col, i;
+
+       out1fmt("\nBuilt-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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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_ASH_MATH_SUPPORT
+static arith_t
+dash_arith(const char *s)
+{
+       arith_t result;
+       int errcode = 0;
+
+       INT_OFF;
+       result = arith(s, &errcode);
+       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;
+}
+
+/*
+ *  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 ATTRIBUTE_UNUSED, char **argv)
+{
+       arith_t i;
+
+       argv++;
+       if (!*argv)
+               ash_msg_and_raise_error("expression expected");
+       do {
+               i = dash_arith(*argv);
+       } while (*++argv);
+
+       return !i;
+}
+#endif /* ASH_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.  The -e option causes backslashes to escape the
+ * following character.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ */
+static int
+readcmd(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       char **ap;
+       int backslash;
+       char c;
+       int rflag;
+       char *prompt;
+       const char *ifs;
+       char *p;
+       int startword;
+       int status;
+       int i;
+#if ENABLE_ASH_READ_NCHARS
+       int n_flag = 0;
+       int nchars = 0;
+       int silent = 0;
+       struct termios tty, old_tty;
+#endif
+#if ENABLE_ASH_READ_TIMEOUT
+       fd_set set;
+       struct timeval ts;
+
+       ts.tv_sec = ts.tv_usec = 0;
+#endif
+
+       rflag = 0;
+       prompt = NULL;
+#if ENABLE_ASH_READ_NCHARS && ENABLE_ASH_READ_TIMEOUT
+       while ((i = nextopt("p:rt:n:s")) != '\0')
+#elif ENABLE_ASH_READ_NCHARS
+       while ((i = nextopt("p:rn:s")) != '\0')
+#elif ENABLE_ASH_READ_TIMEOUT
+       while ((i = nextopt("p:rt:")) != '\0')
+#else
+       while ((i = nextopt("p:r")) != '\0')
+#endif
+       {
+               switch (i) {
+               case 'p':
+                       prompt = optionarg;
+                       break;
+#if ENABLE_ASH_READ_NCHARS
+               case 'n':
+                       nchars = bb_strtou(optionarg, NULL, 10);
+                       if (nchars < 0 || errno)
+                               ash_msg_and_raise_error("invalid count");
+                       n_flag = nchars; /* just a flag "nchars is nonzero" */
+                       break;
+               case 's':
+                       silent = 1;
+                       break;
+#endif
+#if ENABLE_ASH_READ_TIMEOUT
+               case 't':
+                       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");
+                       }
+                       break;
+#endif
+               case 'r':
+                       rflag = 1;
+                       break;
+               default:
+                       break;
+               }
+       }
+       if (prompt && isatty(0)) {
+               out2str(prompt);
+       }
+       ap = argptr;
+       if (*ap == NULL)
+               ash_msg_and_raise_error("arg count");
+       ifs = bltinlookup("IFS");
+       if (ifs == NULL)
+               ifs = defifs;
+#if ENABLE_ASH_READ_NCHARS
+       if (n_flag || silent) {
+               if (tcgetattr(0, &tty) != 0) {
+                       /* Not a tty */
+                       n_flag = 0;
+                       silent = 0;
+               } else {
+                       old_tty = tty;
+                       if (n_flag) {
+                               tty.c_lflag &= ~ICANON;
+                               tty.c_cc[VMIN] = nchars < 256 ? nchars : 255;
+                       }
+                       if (silent) {
+                               tty.c_lflag &= ~(ECHO | ECHOK | ECHONL);
+                       }
+                       tcsetattr(0, TCSANOW, &tty);
+               }
+       }
+#endif
+#if ENABLE_ASH_READ_TIMEOUT
+       if (ts.tv_sec || ts.tv_usec) {
+               FD_ZERO(&set);
+               FD_SET(0, &set);
+
+               /* poll-based wait produces bigger code, using select */
+               i = select(1, &set, NULL, NULL, &ts);
+               if (!i) { /* timed out! */
+#if ENABLE_ASH_READ_NCHARS
+                       if (n_flag)
+                               tcsetattr(0, TCSANOW, &old_tty);
+#endif
+                       return 1;
+               }
+       }
+#endif
+       status = 0;
+       startword = 1;
+       backslash = 0;
+       STARTSTACKSTR(p);
+       do {
+               if (nonblock_safe_read(0, &c, 1) != 1) {
+                       status = 1;
+                       break;
+               }
+               if (c == '\0')
+                       continue;
+               if (backslash) {
+                       backslash = 0;
+                       if (c != '\n')
+                               goto put;
+                       continue;
+               }
+               if (!rflag && c == '\\') {
+                       backslash++;
+                       continue;
+               }
+               if (c == '\n')
+                       break;
+               if (startword && *ifs == ' ' && strchr(ifs, c)) {
+                       continue;
+               }
+               startword = 0;
+               if (ap[1] != NULL && strchr(ifs, c) != NULL) {
+                       STACKSTRNUL(p);
+                       setvar(*ap, stackblock(), 0);
+                       ap++;
+                       startword = 1;
+                       STARTSTACKSTR(p);
+               } else {
+ put:
+                       STPUTC(c, p);
+               }
+       }
+/* end of do {} while: */
+#if ENABLE_ASH_READ_NCHARS
+       while (!n_flag || --nchars);
+#else
+       while (1);
+#endif
+
+#if ENABLE_ASH_READ_NCHARS
+       if (n_flag || silent)
+               tcsetattr(0, TCSANOW, &old_tty);
+#endif
+
+       STACKSTRNUL(p);
+       /* Remove trailing blanks */
+       while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL)
+               *p = '\0';
+       setvar(*ap, stackblock(), 0);
+       while (*++ap != NULL)
+               setvar(*ap, nullstr, 0);
+       return status;
+}
+
+static int
+umaskcmd(int argc ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       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');
+                               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;
+}
+
+
+/* ============ Math support */
+
+#if ENABLE_ASH_MATH_SUPPORT
+
+/* Copyright (c) 2001 Aaron Lehmann <aaronl@vitelus.com>
+
+   Permission is hereby granted, free of charge, to any person obtaining
+   a copy of this software and associated documentation files (the
+   "Software"), to deal in the Software without restriction, including
+   without limitation the rights to use, copy, modify, merge, publish,
+   distribute, sublicense, and/or sell copies of the Software, and to
+   permit persons to whom the Software is furnished to do so, subject to
+   the following conditions:
+
+   The above copyright notice and this permission notice shall be
+   included in all copies or substantial portions of the Software.
+
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/* This is my infix parser/evaluator. It is optimized for size, intended
+ * as a replacement for yacc-based parsers. However, it may well be faster
+ * than a comparable parser written in yacc. The supported operators are
+ * listed in #defines below. Parens, order of operations, and error handling
+ * are supported. This code is thread safe. The exact expression format should
+ * be that which POSIX specifies for shells. */
+
+/* The code uses a simple two-stack algorithm. See
+ * http://www.onthenet.com.au/~grahamis/int2008/week02/lect02.html
+ * for a detailed explanation of the infix-to-postfix algorithm on which
+ * this is based (this code differs in that it applies operators immediately
+ * to the stack instead of adding them to a queue to end up with an
+ * expression). */
+
+/* To use the routine, call it with an expression string and error return
+ * pointer */
+
+/*
+ * Aug 24, 2001              Manuel Novoa III
+ *
+ * Reduced the generated code size by about 30% (i386) and fixed several bugs.
+ *
+ * 1) In arith_apply():
+ *    a) Cached values of *numptr and &(numptr[-1]).
+ *    b) Removed redundant test for zero denominator.
+ *
+ * 2) In arith():
+ *    a) Eliminated redundant code for processing operator tokens by moving
+ *       to a table-based implementation.  Also folded handling of parens
+ *       into the table.
+ *    b) Combined all 3 loops which called arith_apply to reduce generated
+ *       code size at the cost of speed.
+ *
+ * 3) The following expressions were treated as valid by the original code:
+ *       1()  ,    0!  ,    1 ( *3 )   .
+ *    These bugs have been fixed by internally enclosing the expression in
+ *    parens and then checking that all binary ops and right parens are
+ *    preceded by a valid expression (NUM_TOKEN).
+ *
+ * Note: It may be desirable to replace Aaron's test for whitespace with
+ * ctype's isspace() if it is used by another busybox applet or if additional
+ * whitespace chars should be considered.  Look below the "#include"s for a
+ * precompiler test.
+ */
+
+/*
+ * Aug 26, 2001              Manuel Novoa III
+ *
+ * Return 0 for null expressions.  Pointed out by Vladimir Oleynik.
+ *
+ * Merge in Aaron's comments previously posted to the busybox list,
+ * modified slightly to take account of my changes to the code.
+ *
+ */
+
+/*
+ *  (C) 2003 Vladimir Oleynik <dzo@simtreas.ru>
+ *
+ * - allow access to variable,
+ *   used recursive find value indirection (c=2*2; a="c"; $((a+=2)) produce 6)
+ * - realize assign syntax (VAR=expr, +=, *= etc)
+ * - realize exponentiation (** operator)
+ * - realize comma separated - expr, expr
+ * - realise ++expr --expr expr++ expr--
+ * - realise expr ? expr : expr (but, second expr calculate always)
+ * - allow hexadecimal and octal numbers
+ * - was restored loses XOR operator
+ * - remove one goto label, added three ;-)
+ * - protect $((num num)) as true zero expr (Manuel`s error)
+ * - always use special isspace(), see comment from bash ;-)
+ */
+
+#define arith_isspace(arithval) \
+       (arithval == ' ' || arithval == '\n' || arithval == '\t')
+
+typedef unsigned char operator;
+
+/* An operator's token id is a bit of a bitfield. The lower 5 bits are the
+ * precedence, and 3 high bits are an ID unique across operators of that
+ * precedence. The ID portion is so that multiple operators can have the
+ * same precedence, ensuring that the leftmost one is evaluated first.
+ * Consider * and /. */
+
+#define tok_decl(prec,id) (((id)<<5)|(prec))
+#define PREC(op) ((op) & 0x1F)
+
+#define TOK_LPAREN tok_decl(0,0)
+
+#define TOK_COMMA tok_decl(1,0)
+
+#define TOK_ASSIGN tok_decl(2,0)
+#define TOK_AND_ASSIGN tok_decl(2,1)
+#define TOK_OR_ASSIGN tok_decl(2,2)
+#define TOK_XOR_ASSIGN tok_decl(2,3)
+#define TOK_PLUS_ASSIGN tok_decl(2,4)
+#define TOK_MINUS_ASSIGN tok_decl(2,5)
+#define TOK_LSHIFT_ASSIGN tok_decl(2,6)
+#define TOK_RSHIFT_ASSIGN tok_decl(2,7)
+
+#define TOK_MUL_ASSIGN tok_decl(3,0)
+#define TOK_DIV_ASSIGN tok_decl(3,1)
+#define TOK_REM_ASSIGN tok_decl(3,2)
+
+/* all assign is right associativity and precedence eq, but (7+3)<<5 > 256 */
+#define convert_prec_is_assing(prec) do { if (prec == 3) prec = 2; } while (0)
+
+/* conditional is right associativity too */
+#define TOK_CONDITIONAL tok_decl(4,0)
+#define TOK_CONDITIONAL_SEP tok_decl(4,1)
+
+#define TOK_OR tok_decl(5,0)
+
+#define TOK_AND tok_decl(6,0)
+
+#define TOK_BOR tok_decl(7,0)
+
+#define TOK_BXOR tok_decl(8,0)
+
+#define TOK_BAND tok_decl(9,0)
+
+#define TOK_EQ tok_decl(10,0)
+#define TOK_NE tok_decl(10,1)
+
+#define TOK_LT tok_decl(11,0)
+#define TOK_GT tok_decl(11,1)
+#define TOK_GE tok_decl(11,2)
+#define TOK_LE tok_decl(11,3)
+
+#define TOK_LSHIFT tok_decl(12,0)
+#define TOK_RSHIFT tok_decl(12,1)
+
+#define TOK_ADD tok_decl(13,0)
+#define TOK_SUB tok_decl(13,1)
+
+#define TOK_MUL tok_decl(14,0)
+#define TOK_DIV tok_decl(14,1)
+#define TOK_REM tok_decl(14,2)
+
+/* exponent is right associativity */
+#define TOK_EXPONENT tok_decl(15,1)
+
+/* For now unary operators. */
+#define UNARYPREC 16
+#define TOK_BNOT tok_decl(UNARYPREC,0)
+#define TOK_NOT tok_decl(UNARYPREC,1)
+
+#define TOK_UMINUS tok_decl(UNARYPREC+1,0)
+#define TOK_UPLUS tok_decl(UNARYPREC+1,1)
+
+#define PREC_PRE (UNARYPREC+2)
+
+#define TOK_PRE_INC tok_decl(PREC_PRE, 0)
+#define TOK_PRE_DEC tok_decl(PREC_PRE, 1)
+
+#define PREC_POST (UNARYPREC+3)
+
+#define TOK_POST_INC tok_decl(PREC_POST, 0)
+#define TOK_POST_DEC tok_decl(PREC_POST, 1)
+
+#define SPEC_PREC (UNARYPREC+4)
+
+#define TOK_NUM tok_decl(SPEC_PREC, 0)
+#define TOK_RPAREN tok_decl(SPEC_PREC, 1)
+
+#define NUMPTR (*numstackptr)
+
+static int
+tok_have_assign(operator op)
+{
+       operator prec = PREC(op);
+
+       convert_prec_is_assing(prec);
+       return (prec == PREC(TOK_ASSIGN) ||
+                       prec == PREC_PRE || prec == PREC_POST);
+}
+
+static int
+is_right_associativity(operator prec)
+{
+       return (prec == PREC(TOK_ASSIGN) || prec == PREC(TOK_EXPONENT)
+               || prec == PREC(TOK_CONDITIONAL));
+}
+
+typedef struct ARITCH_VAR_NUM {
+       arith_t val;
+       arith_t contidional_second_val;
+       char contidional_second_val_initialized;
+       char *var;      /* if NULL then is regular number,
+                          else is variable name */
+} v_n_t;
+
+typedef struct CHK_VAR_RECURSIVE_LOOPED {
+       const char *var;
+       struct CHK_VAR_RECURSIVE_LOOPED *next;
+} chk_var_recursive_looped_t;
+
+static chk_var_recursive_looped_t *prev_chk_var_recursive;
+
+static int
+arith_lookup_val(v_n_t *t)
+{
+       if (t->var) {
+               const char * p = lookupvar(t->var);
+
+               if (p) {
+                       int errcode;
+
+                       /* recursive try as expression */
+                       chk_var_recursive_looped_t *cur;
+                       chk_var_recursive_looped_t cur_save;
+
+                       for (cur = prev_chk_var_recursive; cur; cur = cur->next) {
+                               if (strcmp(cur->var, t->var) == 0) {
+                                       /* expression recursion loop detected */
+                                       return -5;
+                               }
+                       }
+                       /* save current lookuped var name */
+                       cur = prev_chk_var_recursive;
+                       cur_save.var = t->var;
+                       cur_save.next = cur;
+                       prev_chk_var_recursive = &cur_save;
+
+                       t->val = arith (p, &errcode);
+                       /* restore previous ptr after recursiving */
+                       prev_chk_var_recursive = cur;
+                       return errcode;
+               }
+               /* allow undefined var as 0 */
+               t->val = 0;
+       }
+       return 0;
+}
+
+/* "applying" a token means performing it on the top elements on the integer
+ * stack. For a unary operator it will only change the top element, but a
+ * binary operator will pop two arguments and push a result */
+static int
+arith_apply(operator op, v_n_t *numstack, v_n_t **numstackptr)
+{
+       v_n_t *numptr_m1;
+       arith_t numptr_val, rez;
+       int ret_arith_lookup_val;
+
+       /* 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);
+       if (ret_arith_lookup_val)
+               return ret_arith_lookup_val;
+
+       rez = numptr_m1->val;
+       if (op == TOK_UMINUS)
+               rez *= -1;
+       else if (op == TOK_NOT)
+               rez = !rez;
+       else if (op == TOK_BNOT)
+               rez = ~rez;
+       else if (op == TOK_POST_INC || op == TOK_PRE_INC)
+               rez++;
+       else if (op == TOK_POST_DEC || op == TOK_PRE_DEC)
+               rez--;
+       else if (op != TOK_UPLUS) {
+               /* Binary operators */
+
+               /* check and binary operators need two arguments */
+               if (numptr_m1 == numstack) goto err;
+
+               /* ... and they pop one */
+               --NUMPTR;
+               numptr_val = rez;
+               if (op == TOK_CONDITIONAL) {
+                       if (! numptr_m1->contidional_second_val_initialized) {
+                               /* protect $((expr1 ? expr2)) without ": expr" */
+                               goto err;
+                       }
+                       rez = numptr_m1->contidional_second_val;
+               } else if (numptr_m1->contidional_second_val_initialized) {
+                       /* protect $((expr1 : expr2)) without "expr ? " */
+                       goto err;
+               }
+               numptr_m1 = NUMPTR - 1;
+               if (op != TOK_ASSIGN) {
+                       /* check operand is var with noninteger value for not '=' */
+                       ret_arith_lookup_val = arith_lookup_val(numptr_m1);
+                       if (ret_arith_lookup_val)
+                               return ret_arith_lookup_val;
+               }
+               if (op == TOK_CONDITIONAL) {
+                       numptr_m1->contidional_second_val = rez;
+               }
+               rez = numptr_m1->val;
+               if (op == TOK_BOR || op == TOK_OR_ASSIGN)
+                       rez |= numptr_val;
+               else if (op == TOK_OR)
+                       rez = numptr_val || rez;
+               else if (op == TOK_BAND || op == TOK_AND_ASSIGN)
+                       rez &= numptr_val;
+               else if (op == TOK_BXOR || op == TOK_XOR_ASSIGN)
+                       rez ^= numptr_val;
+               else if (op == TOK_AND)
+                       rez = rez && numptr_val;
+               else if (op == TOK_EQ)
+                       rez = (rez == numptr_val);
+               else if (op == TOK_NE)
+                       rez = (rez != numptr_val);
+               else if (op == TOK_GE)
+                       rez = (rez >= numptr_val);
+               else if (op == TOK_RSHIFT || op == TOK_RSHIFT_ASSIGN)
+                       rez >>= numptr_val;
+               else if (op == TOK_LSHIFT || op == TOK_LSHIFT_ASSIGN)
+                       rez <<= numptr_val;
+               else if (op == TOK_GT)
+                       rez = (rez > numptr_val);
+               else if (op == TOK_LT)
+                       rez = (rez < numptr_val);
+               else if (op == TOK_LE)
+                       rez = (rez <= numptr_val);
+               else if (op == TOK_MUL || op == TOK_MUL_ASSIGN)
+                       rez *= numptr_val;
+               else if (op == TOK_ADD || op == TOK_PLUS_ASSIGN)
+                       rez += numptr_val;
+               else if (op == TOK_SUB || op == TOK_MINUS_ASSIGN)
+                       rez -= numptr_val;
+               else if (op == TOK_ASSIGN || op == TOK_COMMA)
+                       rez = numptr_val;
+               else if (op == TOK_CONDITIONAL_SEP) {
+                       if (numptr_m1 == numstack) {
+                               /* protect $((expr : expr)) without "expr ? " */
+                               goto err;
+                       }
+                       numptr_m1->contidional_second_val_initialized = op;
+                       numptr_m1->contidional_second_val = numptr_val;
+               } else if (op == TOK_CONDITIONAL) {
+                       rez = rez ?
+                               numptr_val : numptr_m1->contidional_second_val;
+               } else if (op == TOK_EXPONENT) {
+                       if (numptr_val < 0)
+                               return -3;      /* exponent less than 0 */
+                       else {
+                               arith_t c = 1;
+
+                               if (numptr_val)
+                                       while (numptr_val--)
+                                               c *= rez;
+                               rez = c;
+                       }
+               } else if (numptr_val==0)          /* zero divisor check */
+                       return -2;
+               else if (op == TOK_DIV || op == TOK_DIV_ASSIGN)
+                       rez /= numptr_val;
+               else if (op == TOK_REM || op == TOK_REM_ASSIGN)
+                       rez %= numptr_val;
+       }
+       if (tok_have_assign(op)) {
+               char buf[sizeof(arith_t_type)*3 + 2];
+
+               if (numptr_m1->var == NULL) {
+                       /* Hmm, 1=2 ? */
+                       goto err;
+               }
+               /* save to shell variable */
+#if ENABLE_ASH_MATH_SUPPORT_64
+               snprintf(buf, sizeof(buf), "%lld", (arith_t_type) rez);
+#else
+               snprintf(buf, sizeof(buf), "%ld", (arith_t_type) rez);
+#endif
+               setvar(numptr_m1->var, buf, 0);
+               /* after saving, make previous value for v++ or v-- */
+               if (op == TOK_POST_INC)
+                       rez--;
+               else if (op == TOK_POST_DEC)
+                       rez++;
+       }
+       numptr_m1->val = rez;
+       /* protect geting var value, is number now */
+       numptr_m1->var = NULL;
+       return 0;
+ err:
+       return -1;
+}
+
+/* longest must 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]
+
+static arith_t
+arith(const char *expr, int *perrcode)
+{
+       char arithval; /* Current character under analysis */
+       operator lasttok, op;
+       operator prec;
+
+       const char *p = endexpression;
+       int errcode;
+
+       size_t datasizes = strlen(expr) + 2;
+
+       /* Stack of integers */
+       /* The proof that there can be no more than strlen(startbuf)/2+1 integers
+        * in any given correct or incorrect expression is left as an exercise to
+        * the reader. */
+       v_n_t *numstack = alloca(((datasizes)/2)*sizeof(v_n_t)),
+                               *numstackptr = numstack;
+       /* Stack of operator tokens */
+       operator *stack = alloca((datasizes) * sizeof(operator)),
+                               *stackptr = stack;
+
+       *stackptr++ = lasttok = TOK_LPAREN;     /* start off with a left paren */
+       *perrcode = errcode = 0;
+
+       while (1) {
+               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:
+                               return (*perrcode = -1);
+                       }
+                       if (numstack->var) {
+                               /* expression is $((var)) only, lookup now */
+                               errcode = arith_lookup_val(numstack);
+                       }
+ 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;
+#if ENABLE_ASH_MATH_SUPPORT_64
+                       numstackptr->val = strtoll(expr, (char **) &expr, 0);
+#else
+                       numstackptr->val = strtol(expr, (char **) &expr, 0);
+#endif
+                       goto num;
+               }
+               for (p = op_tokens; ; p++) {
+                       const char *o;
+
+                       if (*p == 0) {
+                               /* strange operator not found */
+                               goto err;
+                       }
+                       for (o = expr; *p && *o == *p; p++)
+                               o++;
+                       if (! *p) {
+                               /* found */
+                               expr = o - 1;
+                               break;
+                       }
+                       /* skip tail uncompared token */
+                       while (*p)
+                               p++;
+                       /* skip zero delim */
+                       p++;
+               }
+               op = p[1];
+
+               /* post grammar: a++ reduce to num */
+               if (lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC)
+                       lasttok = TOK_NUM;
+
+               /* Plus and minus are binary (not unary) _only_ if the last
+                * token was as number, or a right paren (which pretends to be
+                * a number, since it evaluates to one). Think about it.
+                * It makes sense. */
+               if (lasttok != TOK_NUM) {
+                       switch (op) {
+                       case TOK_ADD:
+                               op = TOK_UPLUS;
+                               break;
+                       case TOK_SUB:
+                               op = TOK_UMINUS;
+                               break;
+                       case TOK_POST_INC:
+                               op = TOK_PRE_INC;
+                               break;
+                       case TOK_POST_DEC:
+                               op = TOK_PRE_DEC;
+                               break;
+                       }
+               }
+               /* We don't want a unary operator to cause recursive descent on the
+                * stack, because there can be many in a row and it could cause an
+                * operator to be evaluated before its argument is pushed onto the
+                * integer stack. */
+               /* But for binary operators, "apply" everything on the operator
+                * stack until we find an operator with a lesser priority than the
+                * one we have just extracted. */
+               /* Left paren is given the lowest priority so it will never be
+                * "applied" in this way.
+                * if associativity is right and priority eq, applied also skip
+                */
+               prec = PREC(op);
+               if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) {
+                       /* not left paren or unary */
+                       if (lasttok != TOK_NUM) {
+                               /* binary op must be preceded by a num */
+                               goto err;
+                       }
+                       while (stackptr != stack) {
+                               if (op == TOK_RPAREN) {
+                                       /* The algorithm employed here is simple: while we don't
+                                        * hit an open paren nor the bottom of the stack, pop
+                                        * tokens and apply them */
+                                       if (stackptr[-1] == TOK_LPAREN) {
+                                               --stackptr;
+                                               /* Any operator directly after a */
+                                               lasttok = TOK_NUM;
+                                               /* close paren should consider itself binary */
+                                               goto prologue;
+                                       }
+                               } else {
+                                       operator prev_prec = PREC(stackptr[-1]);
+
+                                       convert_prec_is_assing(prec);
+                                       convert_prec_is_assing(prev_prec);
+                                       if (prev_prec < prec)
+                                               break;
+                                       /* check right assoc */
+                                       if (prev_prec == prec && is_right_associativity(prec))
+                                               break;
+                               }
+                               errcode = arith_apply(*--stackptr, numstack, &numstackptr);
+                               if (errcode) goto ret;
+                       }
+                       if (op == TOK_RPAREN) {
+                               goto err;
+                       }
+               }
+
+               /* Push this operator to the stack and remember it. */
+               *stackptr++ = lasttok = op;
+ prologue:
+               ++expr;
+       } /* while */
+}
+#endif /* ASH_MATH_SUPPORT */
+
+
+/* ============ main() and helpers */
+
+/*
+ * Called to exit the shell.
+ */
+static void exitshell(void) ATTRIBUTE_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 == 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.nextc = 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: */
+       parselleft = parsenleft = 0;      /* clear input buffer */
+       popallfiles();
+       /* from parser.c: */
+       tokpushback = 0;
+       checkkwd = 0;
+       /* from redir.c: */
+       clearredir(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 ATTRIBUTE_UNUSED, char **argv)
+{
+       char *shinit;
+       volatile int 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)) {
+               int e;
+               int s;
+
+               reset();
+
+               e = exception;
+               if (e == EXERROR)
+                       exitstatus = 2;
+               s = state;
+               if (e == EXEXIT || s == 0 || iflag == 0 || shlvl)
+                       exitshell();
+
+               if (e == EXINT) {
+                       outcslow('\n', stderr);
+               }
+               popstackmark(&smark);
+               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_puts("Shell args: ");
+       trace_puts_args(argv);
+#endif
+       rootpid = getpid();
+
+#if ENABLE_ASH_RANDOM_SUPPORT
+       rseed = rootpid + time(NULL);
+#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(minusc, 0);
+
+       if (sflag || minusc == NULL) {
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+               if ( iflag ) {
+                       const char *hp = lookupvar("HISTFILE");
+
+                       if (hp != NULL)
+                               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 */
+}
+
+#if DEBUG
+const char *applet_name = "debug stuff usage";
+int main(int argc, char **argv)
+{
+       return ash_main(argc, argv);
+}
+#endif
+
+
+/*-
+ * Copyright (c) 1989, 1991, 1993, 1994
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/shell/ash_doc.txt b/shell/ash_doc.txt
new file mode 100644 (file)
index 0000000..28c5748
--- /dev/null
@@ -0,0 +1,31 @@
+       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
diff --git a/shell/ash_ptr_hack.c b/shell/ash_ptr_hack.c
new file mode 100644 (file)
index 0000000..490b73b
--- /dev/null
@@ -0,0 +1,16 @@
+/* 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.
+ */
+
+/* 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;
+struct globals_memstack;
+struct globals_var;
+
+struct globals_misc     *ash_ptr_to_globals_misc;
+struct globals_memstack *ash_ptr_to_globals_memstack;
+struct globals_var      *ash_ptr_to_globals_var;
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-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-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-signals/signal1.right b/shell/ash_test/ash-signals/signal1.right
new file mode 100644 (file)
index 0000000..66c30a5
--- /dev/null
@@ -0,0 +1,20 @@
+got signal
+trap -- 'echo got signal' USR1
+sent 1 signal
+got signal
+sleep interrupted
+trap -- 'echo got signal' USR1
+sent 2 signal
+got signal
+sleep interrupted
+trap -- 'echo got signal' USR1
+sent 3 signal
+got signal
+sleep interrupted
+trap -- 'echo got signal' USR1
+sent 4 signal
+got signal
+sleep 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..49a395b
--- /dev/null
@@ -0,0 +1,24 @@
+sleeping=true
+
+trap "echo got signal" USR1
+
+for try in 1 2 3 4 5; do
+    kill -USR1 $$
+    sleep 1
+    echo sent $try signal
+done &
+
+sleep 10 &
+
+while $sleeping; do
+    trap
+    if wait %%; then
+        echo sleep completed
+        sleeping=false
+    elif [ $? == 127 ]; then
+        echo no sleep tonite
+        sleeping=false
+    else
+        echo sleep interrupted;
+    fi
+done
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/printenv.c b/shell/ash_test/printenv.c
new file mode 100644 (file)
index 0000000..06df21f
--- /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 (0);
+    }
+
+  /* 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 (0);
+           }
+       }
+    }
+  exit (1);
+}
diff --git a/shell/ash_test/recho.c b/shell/ash_test/recho.c
new file mode 100644 (file)
index 0000000..02be0d7
--- /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(0);
+}
+
+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..e023338
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+test -x ash || {
+    echo "No ./ash?! Perhaps you want to run 'ln -s ../../busybox ash'"
+    exit
+}
+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"
+    (
+    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
+       {
+           "$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 ash-*`
+
+    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..621d06d
--- /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(0);
+}
diff --git a/shell/bbsh.c b/shell/bbsh.c
new file mode 100644 (file)
index 0000000..02e6050
--- /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(1);
+               } else waitpid(pid, &status, 0);
+       }
+
+       return 0;
+}
+
+static void free_cmd(void *data)
+{
+       struct command *cmd=(struct command *)data;
+
+       while (cmd->argc) free(cmd->argv[--cmd->argc]);
+}
+
+
+static void handle(char *command)
+{
+       struct pipeline line;
+       char *start = command;
+
+       for (;;) {
+               memset(&line,0,sizeof(struct pipeline));
+               start = parse_pipeline(start, &line);
+               if (!line.cmd) break;
+
+               run_pipeline(&line);
+               free_list(line.cmd, free_cmd);
+       }
+}
+
+int bbsh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int bbsh_main(int argc, char **argv)
+{
+       char *command=NULL;
+       FILE *f;
+
+       getopt32(argv, "c:", &command);
+
+       f = argv[optind] ? xfopen(argv[optind],"r") : NULL;
+       if (command) handle(command);
+       else {
+               unsigned cmdlen=0;
+               for (;;) {
+                       if (!f) putchar('$');
+                       if (1 > getline(&command, &cmdlen,f ? : stdin)) break;
+
+                       handle(command);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP) free(command);
+       }
+
+       return 1;
+}
diff --git a/shell/cttyhack.c b/shell/cttyhack.c
new file mode 100644 (file)
index 0000000..bbe5149
--- /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 ATTRIBUTE_UNUSED, 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..4e6d500
--- /dev/null
@@ -0,0 +1,3952 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * sh.c -- a prototype Bourne shell grammar parser
+ *      Intended to follow the original Thompson and Ritchie
+ *      "small and simple is beautiful" philosophy, which
+ *      incidentally is a good match to today's BusyBox.
+ *
+ * Copyright (C) 2000,2001  Larry Doolittle  <larry@doolittle.boa.org>
+ *
+ * Credits:
+ *      The parser routines proper are all original material, first
+ *      written Dec 2000 and Jan 2001 by Larry Doolittle.  The
+ *      execution engine, the builtins, and much of the underlying
+ *      support has been adapted from busybox-0.49pre's lash, which is
+ *      Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *      written by Erik Andersen <andersen@codepoet.org>.  That, in turn,
+ *      is based in part on ladsh.c, by Michael K. Johnson and Erik W.
+ *      Troan, which they placed in the public domain.  I don't know
+ *      how much of the Johnson/Troan code has survived the repeated
+ *      rewrites.
+ *
+ * Other credits:
+ *      b_addchr() derived from similar w_addchar function in glibc-2.2
+ *      setup_redirect(), redirect_opt_num(), and big chunks of main()
+ *      and many builtins derived from contributions by Erik Andersen
+ *      miscellaneous bugfixes from Matt Kraai
+ *
+ * There are two big (and related) architecture differences between
+ * this parser and the lash parser.  One is that this version is
+ * actually designed from the ground up to understand nearly all
+ * of the Bourne grammar.  The second, consequential change is that
+ * the parser and input reader have been turned inside out.  Now,
+ * the parser is in control, and asks for input as needed.  The old
+ * way had the input reader in control, and it asked for parsing to
+ * take place as needed.  The new way makes it much easier to properly
+ * handle the recursion implicit in the various substitutions, especially
+ * across continuation lines.
+ *
+ * Bash grammar not implemented: (how many of these were in original sh?)
+ *      $_
+ *      ! negation operator for pipes
+ *      &> and >& redirection of stdout+stderr
+ *      Brace Expansion
+ *      Tilde Expansion
+ *      fancy forms of Parameter Expansion
+ *      aliases
+ *      Arithmetic Expansion
+ *      <(list) and >(list) Process Substitution
+ *      reserved words: case, esac, select, function
+ *      Here Documents ( << word )
+ *      Functions
+ * Major bugs:
+ *      job handling woefully incomplete and buggy (improved --vda)
+ *      reserved word execution woefully incomplete and buggy
+ * to-do:
+ *      port selected bugfixes from post-0.49 busybox lash - done?
+ *      finish implementing reserved words: for, while, until, do, done
+ *      change { and } from special chars to reserved words
+ *      builtins: break, continue, eval, return, set, trap, ulimit
+ *      test magic exec
+ *      handle children going into background
+ *      clean up recognition of null pipes
+ *      check setting of global_argc and global_argv
+ *      control-C handling, probably with longjmp
+ *      follow IFS rules more precisely, including update semantics
+ *      figure out what to do with backslash-newline
+ *      explain why we use signal instead of sigaction
+ *      propagate syntax errors, die on resource errors?
+ *      continuation lines, both explicit and implicit - done?
+ *      memory leak finding and plugging - done?
+ *      more testing, especially quoting rules and redirection
+ *      document how quoting rules not precisely followed for variable assignments
+ *      maybe change charmap[] to use 2-bit entries
+ *      (eventually) remove all the printf's
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+
+#include <glob.h>      /* glob, of course */
+#include <getopt.h>    /* should be pretty obvious */
+/* #include <dmalloc.h> */
+
+#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
+
+
+#if !BB_MMU && ENABLE_HUSH_TICK
+//#undef ENABLE_HUSH_TICK
+//#define ENABLE_HUSH_TICK 0
+#warning On NOMMU, hush command substitution is dangerous.
+#warning Dont use it for commands which produce lots of output.
+#warning For more info see shell/hush.c, generate_stream_from_list().
+#endif
+
+#if !BB_MMU && ENABLE_HUSH_JOB
+#undef ENABLE_HUSH_JOB
+#define ENABLE_HUSH_JOB 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
+
+
+/* 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_jobs(...)   do {} while (0)
+#define debug_printf_expand(...) do {} while (0)
+#define debug_printf_clean(...)  do {} while (0)
+
+#ifndef debug_printf
+#define debug_printf(...) fprintf(stderr, __VA_ARGS__)
+#endif
+
+#ifndef debug_printf_parse
+#define debug_printf_parse(...) fprintf(stderr, __VA_ARGS__)
+#endif
+
+#ifndef debug_printf_exec
+#define debug_printf_exec(...) fprintf(stderr, __VA_ARGS__)
+#endif
+
+#ifndef debug_printf_jobs
+#define debug_printf_jobs(...) fprintf(stderr, __VA_ARGS__)
+#define DEBUG_SHELL_JOBS 1
+#endif
+
+#ifndef debug_printf_expand
+#define debug_printf_expand(...) fprintf(stderr, __VA_ARGS__)
+#define DEBUG_EXPAND 1
+#endif
+
+/* Keep unconditionally on for now */
+#define ENABLE_HUSH_DEBUG 1
+
+#ifndef debug_printf_clean
+/* broken, of course, but OK for testing */
+static const char *indenter(int i)
+{
+       static const char blanks[] ALIGN1 =
+               "                                    ";
+       return &blanks[sizeof(blanks) - i - 1];
+}
+#define debug_printf_clean(...) fprintf(stderr, __VA_ARGS__)
+#define DEBUG_CLEAN 1
+#endif
+
+
+/*
+ * Leak hunting. Use hush_leaktool.sh for post-processing.
+ */
+#ifdef FOR_HUSH_LEAKTOOL
+void *xxmalloc(int lineno, size_t size)
+{
+       void *ptr = xmalloc((size + 0xff) & ~0xff);
+       fprintf(stderr, "line %d: malloc %p\n", lineno, ptr);
+       return ptr;
+}
+void *xxrealloc(int lineno, void *ptr, size_t size)
+{
+       ptr = xrealloc(ptr, (size + 0xff) & ~0xff);
+       fprintf(stderr, "line %d: realloc %p\n", lineno, ptr);
+       return ptr;
+}
+char *xxstrdup(int lineno, const char *str)
+{
+       char *ptr = xstrdup(str);
+       fprintf(stderr, "line %d: strdup %p\n", lineno, ptr);
+       return ptr;
+}
+void xxfree(void *ptr)
+{
+       fprintf(stderr, "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
+
+
+#define SPECIAL_VAR_SYMBOL   3
+
+#define PARSEFLAG_EXIT_FROM_LOOP 1
+#define PARSEFLAG_SEMICOLON      (1 << 1)  /* symbol ';' is special for parser */
+#define PARSEFLAG_REPARSING      (1 << 2)  /* >= 2nd pass */
+
+typedef enum {
+       REDIRECT_INPUT     = 1,
+       REDIRECT_OVERWRITE = 2,
+       REDIRECT_APPEND    = 3,
+       REDIRECT_HEREIS    = 4,
+       REDIRECT_IO        = 5
+} redir_type;
+
+/* The descrip member of this structure is only used to make debugging
+ * output pretty */
+static const struct {
+       int mode;
+       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,                 -1, "<<" },
+       { O_RDWR,                    1, "<>" }
+};
+
+typedef enum {
+       PIPE_SEQ = 1,
+       PIPE_AND = 2,
+       PIPE_OR  = 3,
+       PIPE_BG  = 4,
+} pipe_style;
+
+/* might eventually control execution */
+typedef enum {
+       RES_NONE  = 0,
+#if ENABLE_HUSH_IF
+       RES_IF    = 1,
+       RES_THEN  = 2,
+       RES_ELIF  = 3,
+       RES_ELSE  = 4,
+       RES_FI    = 5,
+#endif
+#if ENABLE_HUSH_LOOPS
+       RES_FOR   = 6,
+       RES_WHILE = 7,
+       RES_UNTIL = 8,
+       RES_DO    = 9,
+       RES_DONE  = 10,
+       RES_IN    = 11,
+#endif
+       RES_XXXX  = 12,
+       RES_SNTX  = 13
+} reserved_style;
+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
+       FLAG_START = (1 << RES_XXXX ),
+};
+
+/* This holds pointers to the various results of parsing */
+struct p_context {
+       struct child_prog *child;
+       struct pipe *list_head;
+       struct pipe *pipe;
+       struct redir_struct *pending_redirect;
+       smallint res_w;
+       smallint parse_type;        /* bitmask of PARSEFLAG_xxx, defines type of parser : ";$" common or special symbol */
+       int old_flag;               /* bitmask of FLAG_xxx, for figuring out valid reserved words */
+       struct p_context *stack;
+       /* How about quoting status? */
+};
+
+struct redir_struct {
+       struct redir_struct *next;  /* pointer to the next redirect in the list */
+       redir_type type;            /* type of redirection */
+       int fd;                     /* file descriptor being redirected */
+       int dup;                    /* -1, or file descriptor being duplicated */
+       char **glob_word;           /* *word.gl_pathv is the filename */
+};
+
+struct child_prog {
+       pid_t pid;                  /* 0 if exited */
+       char **argv;                /* program name and arguments */
+       struct pipe *group;         /* if non-NULL, first in group or subshell */
+       smallint subshell;          /* flag, non-zero if group must be forked */
+       smallint is_stopped;        /* is the program currently running? */
+       struct redir_struct *redirects; /* I/O redirections */
+       struct pipe *family;        /* pointer back to the child's parent pipe */
+       //sp counting seems to be broken... so commented out, grep for '//sp:'
+       //sp: int sp;               /* number of SPECIAL_VAR_SYMBOL */
+       //seems to be unused, grep for '//pt:'
+       //pt: int parse_type;
+};
+/* 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 .$*.
+ */
+
+struct pipe {
+       struct pipe *next;
+       int num_progs;              /* total number of programs in job */
+       int running_progs;          /* number of programs running (not exited) */
+       int stopped_progs;          /* number of programs 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
+       char *cmdbuf;               /* buffer various argv's point into */
+       struct child_prog *progs;   /* array of commands in pipe */
+       int job_context;            /* bitmask defining current context */
+       smallint followup;          /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
+       smallint res_word;          /* needed for if, for, while, until... */
+};
+
+/* 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;
+};
+
+typedef struct {
+       char *data;
+       int length;
+       int maxlen;
+       smallint o_quote;
+       smallint nonnull;
+} o_string;
+#define NULL_O_STRING {NULL,0,0,0,0}
+/* used for initialization: o_string foo = NULL_O_STRING; */
+
+/* I can almost use ordinary FILE *.  Is open_memstream() universally
+ * available?  Where is it documented? */
+struct in_str {
+       const char *p;
+       /* 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 *);
+};
+#define b_getch(input) ((input)->get(input))
+#define b_peek(input) ((input)->peek(input))
+
+enum {
+       CHAR_ORDINARY           = 0,
+       CHAR_ORDINARY_IF_QUOTED = 1, /* example: *, # */
+       CHAR_IFS                = 2, /* treated as ordinary if quoted */
+       CHAR_SPECIAL            = 3, /* example: $ */
+};
+
+#define HUSH_VER_STR "0.02"
+
+/* "Globals" within this file */
+
+/* Sorted roughly by size (smaller offsets == smaller code) */
+struct globals {
+#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;
+#endif
+#if ENABLE_FEATURE_EDITING
+       line_input_t *line_input_state;
+#endif
+#if ENABLE_HUSH_JOB
+       int run_list_level;
+       pid_t saved_task_pgrp;
+       pid_t saved_tty_pgrp;
+       int last_jobid;
+       struct pipe *job_list;
+       struct pipe *toplevel_list;
+       smallint ctrl_z_flag;
+#endif
+       smallint fake_mode;
+       /* these three support $?, $#, and $1 */
+       char **global_argv;
+       int global_argc;
+       int last_return_code;
+       const char *ifs;
+       const char *cwd;
+       unsigned last_bg_pid;
+       struct variable *top_var; /* = &shell_ver (set in main()) */
+       struct variable shell_ver;
+#if ENABLE_FEATURE_SH_STANDALONE
+       struct nofork_save_area nofork_save;
+#endif
+#if ENABLE_HUSH_JOB
+       sigjmp_buf toplevel_jb;
+#endif
+       unsigned char charmap[256];
+       char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
+};
+
+#define G (*ptr_to_globals)
+
+#if !ENABLE_HUSH_INTERACTIVE
+enum { interactive_fd = 0 };
+#endif
+#if !ENABLE_HUSH_JOB
+enum { run_list_level = 0 };
+#endif
+
+#if ENABLE_HUSH_INTERACTIVE
+#define interactive_fd   (G.interactive_fd  )
+#define PS1              (G.PS1             )
+#define PS2              (G.PS2             )
+#endif
+#if ENABLE_FEATURE_EDITING
+#define line_input_state (G.line_input_state)
+#endif
+#if ENABLE_HUSH_JOB
+#define run_list_level   (G.run_list_level  )
+#define saved_task_pgrp  (G.saved_task_pgrp )
+#define saved_tty_pgrp   (G.saved_tty_pgrp  )
+#define last_jobid       (G.last_jobid      )
+#define job_list         (G.job_list        )
+#define toplevel_list    (G.toplevel_list   )
+#define toplevel_jb      (G.toplevel_jb     )
+#define ctrl_z_flag      (G.ctrl_z_flag     )
+#endif /* JOB */
+#define global_argv      (G.global_argv     )
+#define global_argc      (G.global_argc     )
+#define last_return_code (G.last_return_code)
+#define ifs              (G.ifs             )
+#define fake_mode        (G.fake_mode       )
+#define cwd              (G.cwd             )
+#define last_bg_pid      (G.last_bg_pid     )
+#define top_var          (G.top_var         )
+#define shell_ver        (G.shell_ver       )
+#if ENABLE_FEATURE_SH_STANDALONE
+#define nofork_save      (G.nofork_save     )
+#endif
+#if ENABLE_HUSH_JOB
+#define toplevel_jb      (G.toplevel_jb     )
+#endif
+#define charmap          (G.charmap         )
+#define user_input_buf   (G.user_input_buf  )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+#define B_CHUNK  100
+#define B_NOSPAC 1
+#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
+
+#if 1
+/* Normal */
+static void syntax(const char *msg)
+{
+       /* Was using fancy stuff:
+        * (interactive_fd ? bb_error_msg : bb_error_msg_and_die)(...params...)
+        * but it SEGVs. ?! Oh well... explicit temp ptr works around that */
+       void (*fp)(const char *s, ...);
+
+       fp = (interactive_fd ? bb_error_msg : bb_error_msg_and_die);
+       fp(msg ? "%s: %s" : "syntax error", "syntax error", msg);
+}
+
+#else
+/* Debug */
+static void syntax_lineno(int line)
+{
+       void (*fp)(const char *s, ...);
+
+       fp = (interactive_fd ? bb_error_msg : bb_error_msg_and_die);
+       fp("syntax error hush.c:%d", line);
+}
+#define syntax(str) syntax_lineno(__LINE__)
+#endif
+
+/* Index of subroutines: */
+/*   o_string manipulation: */
+static int b_check_space(o_string *o, int len);
+static int b_addchr(o_string *o, int ch);
+static void b_reset(o_string *o);
+static int b_addqchr(o_string *o, int ch, int quote);
+/*  in_str manipulations: */
+static int static_get(struct in_str *i);
+static int static_peek(struct in_str *i);
+static int file_get(struct in_str *i);
+static int file_peek(struct in_str *i);
+static void setup_file_in_str(struct in_str *i, FILE *f);
+static void setup_string_in_str(struct in_str *i, const char *s);
+/*  "run" the final data structures: */
+#if !defined(DEBUG_CLEAN)
+#define free_pipe_list(head, indent) free_pipe_list(head)
+#define free_pipe(pi, indent)        free_pipe(pi)
+#endif
+static int free_pipe_list(struct pipe *head, int indent);
+static int free_pipe(struct pipe *pi, int indent);
+/*  really run the final data structures: */
+static int setup_redirects(struct child_prog *prog, int squirrel[]);
+static int run_list(struct pipe *pi);
+static void pseudo_exec_argv(char **argv) ATTRIBUTE_NORETURN;
+static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN;
+static int run_pipe(struct pipe *pi);
+/*   extended glob support: */
+static char **globhack(const char *src, char **strings);
+static int glob_needed(const char *s);
+static int xglob(o_string *dest, char ***pglob);
+/*   variable assignment: */
+static int is_assignment(const char *s);
+/*   data structure manipulation: */
+static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input);
+static void initialize_context(struct p_context *ctx);
+static int done_word(o_string *dest, struct p_context *ctx);
+static int done_command(struct p_context *ctx);
+static int done_pipe(struct p_context *ctx, pipe_style type);
+/*   primary string parsing: */
+static int redirect_dup_num(struct in_str *input);
+static int redirect_opt_num(o_string *o);
+#if ENABLE_HUSH_TICK
+static int process_command_subs(o_string *dest, /*struct p_context *ctx,*/
+               struct in_str *input, const char *subst_end);
+#endif
+static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch);
+static const char *lookup_param(const char *src);
+static int handle_dollar(o_string *dest, /*struct p_context *ctx,*/
+               struct in_str *input);
+static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, const char *end_trigger);
+/*   setup: */
+static int parse_and_run_stream(struct in_str *inp, int parse_flag);
+static int parse_and_run_string(const char *s, int parse_flag);
+static int parse_and_run_file(FILE *f);
+/*   job management: */
+static int checkjobs(struct pipe* fg_pipe);
+#if ENABLE_HUSH_JOB
+static int checkjobs_and_fg_shell(struct pipe* fg_pipe);
+static void insert_bg_job(struct pipe *pi);
+static void remove_bg_job(struct pipe *pi);
+static void delete_finished_bg_job(struct pipe *pi);
+#else
+int checkjobs_and_fg_shell(struct pipe* fg_pipe); /* never called */
+#endif
+/*     local variable support */
+static char **expand_strvec_to_strvec(char **argv);
+/* used for eval */
+static char *expand_strvec_to_string(char **argv);
+/* used for expansion of right hand of assignments */
+static char *expand_string_to_string(const char *str);
+static struct variable *get_local_var(const char *name);
+static int set_local_var(char *str, int flg_export);
+static void unset_local_var(const char *name);
+
+
+static char **add_strings_to_strings(int need_xstrdup, char **strings, char **add)
+{
+       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_xstrdup ? xstrdup(add[i]) : add[i];
+       return v;
+}
+
+/* 'add' should be a malloced pointer */
+static char **add_string_to_strings(char **strings, char *add)
+{
+       char *v[2];
+
+       v[0] = add;
+       v[1] = NULL;
+
+       return add_strings_to_strings(0, strings, v);
+}
+
+static void free_strings(char **strings)
+{
+       if (strings) {
+               char **v = strings;
+               while (*v)
+                       free(*v++);
+               free(strings);
+       }
+}
+
+
+/* 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
+static int builtin_pwd(char **argv);
+static int builtin_read(char **argv);
+static int builtin_test(char **argv);
+static int builtin_set(char **argv);
+static int builtin_shift(char **argv);
+static int builtin_source(char **argv);
+static int builtin_umask(char **argv);
+static int builtin_unset(char **argv);
+//static int builtin_not_written(char **argv);
+
+/* 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;                /* name */
+       int (*function) (char **argv);  /* function ptr */
+#if ENABLE_HUSH_HELP
+       const char *descr;              /* description */
+#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_test, "Test condition"),
+       BLTIN("[["    , builtin_test, "Test condition"),
+#if ENABLE_HUSH_JOB
+       BLTIN("bg"    , builtin_fg_bg, "Resume a job in the background"),
+#endif
+//     BLTIN("break" , builtin_not_written, "Exit for, while or until loop"),
+       BLTIN("cd"    , builtin_cd, "Change working directory"),
+//     BLTIN("continue", builtin_not_written, "Continue for, while or until loop"),
+       BLTIN("echo"  , builtin_echo, "Write strings to stdout"),
+       BLTIN("eval"  , builtin_eval, "Construct and run shell command"),
+       BLTIN("exec"  , builtin_exec, "Exec command, replacing this shell with the exec'd process"),
+       BLTIN("exit"  , builtin_exit, "Exit from shell"),
+       BLTIN("export", builtin_export, "Set environment variable"),
+#if ENABLE_HUSH_JOB
+       BLTIN("fg"    , builtin_fg_bg, "Bring job into the foreground"),
+       BLTIN("jobs"  , builtin_jobs, "Lists the active jobs"),
+#endif
+// TODO: remove pwd? we have it as an applet...
+       BLTIN("pwd"   , builtin_pwd, "Print current directory"),
+       BLTIN("read"  , builtin_read, "Input environment variable"),
+//     BLTIN("return", builtin_not_written, "Return from a function"),
+       BLTIN("set"   , builtin_set, "Set/unset shell local variables"),
+       BLTIN("shift" , builtin_shift, "Shift positional parameters"),
+//     BLTIN("trap"  , builtin_not_written, "Trap signals"),
+       BLTIN("test"  , builtin_test, "Test condition"),
+//     BLTIN("ulimit", builtin_not_written, "Controls resource limits"),
+       BLTIN("umask" , builtin_umask, "Sets file creation mask"),
+       BLTIN("unset" , builtin_unset, "Unset environment variable"),
+       BLTIN("."     , builtin_source, "Source-in and run commands in a file"),
+#if ENABLE_HUSH_HELP
+       BLTIN("help"  , builtin_help, "List shell built-in commands"),
+#endif
+       BLTIN(NULL, NULL, NULL)
+};
+
+#if ENABLE_HUSH_JOB
+
+/* Signals are grouped, we handle them in batches */
+static void set_fatal_sighandler(void (*handler)(int))
+{
+       bb_signals(0
+               + (1 << SIGILL)
+               + (1 << SIGTRAP)
+               + (1 << SIGABRT)
+               + (1 << SIGFPE)
+               + (1 << SIGBUS)
+               + (1 << SIGSEGV)
+       /* bash 3.2 seems to handle these just like 'fatal' ones */
+               + (1 << SIGHUP)
+               + (1 << SIGPIPE)
+               + (1 << SIGALRM)
+               , handler);
+}
+static void set_jobctrl_sighandler(void (*handler)(int))
+{
+       bb_signals(0
+               + (1 << SIGTSTP)
+               + (1 << SIGTTIN)
+               + (1 << SIGTTOU)
+               , handler);
+}
+static void set_misc_sighandler(void (*handler)(int))
+{
+       bb_signals(0
+               + (1 << SIGINT)
+               + (1 << SIGQUIT)
+               + (1 << SIGTERM)
+               , handler);
+}
+/* SIGCHLD is special and handled separately */
+
+static void set_every_sighandler(void (*handler)(int))
+{
+       set_fatal_sighandler(handler);
+       set_jobctrl_sighandler(handler);
+       set_misc_sighandler(handler);
+       signal(SIGCHLD, handler);
+}
+
+static void handler_ctrl_c(int sig ATTRIBUTE_UNUSED)
+{
+       debug_printf_jobs("got sig %d\n", sig);
+// as usual we can have all kinds of nasty problems with leaked malloc data here
+       siglongjmp(toplevel_jb, 1);
+}
+
+static void handler_ctrl_z(int sig ATTRIBUTE_UNUSED)
+{
+       pid_t pid;
+
+       debug_printf_jobs("got tty sig %d in pid %d\n", sig, getpid());
+       pid = fork();
+       if (pid < 0) /* can't fork. Pretend there was no ctrl-Z */
+               return;
+       ctrl_z_flag = 1;
+       if (!pid) { /* child */
+               if (ENABLE_HUSH_JOB)
+                       die_sleep = 0; /* let nofork's xfuncs die */
+               setpgrp();
+               debug_printf_jobs("set pgrp for child %d ok\n", getpid());
+               set_every_sighandler(SIG_DFL);
+               raise(SIGTSTP); /* resend TSTP so that child will be stopped */
+               debug_printf_jobs("returning in child\n");
+               /* return to nofork, it will eventually exit now,
+                * not return back to shell */
+               return;
+       }
+       /* parent */
+       /* finish filling up pipe info */
+       toplevel_list->pgrp = pid; /* child is in its own pgrp */
+       toplevel_list->progs[0].pid = pid;
+       /* parent needs to longjmp out of running nofork.
+        * we will "return" exitcode 0, with child put in background */
+// as usual we can have all kinds of nasty problems with leaked malloc data here
+       debug_printf_jobs("siglongjmp in parent\n");
+       siglongjmp(toplevel_jb, 1);
+}
+
+/* Restores tty foreground process group, and exits.
+ * May be called as signal handler for fatal signal
+ * (will faithfully 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) ATTRIBUTE_NORETURN;
+static void sigexit(int sig)
+{
+       /* Disable all signals: job control, SIGPIPE, etc. */
+       sigprocmask_allsigs(SIG_BLOCK);
+
+       if (interactive_fd)
+               tcsetpgrp(interactive_fd, saved_tty_pgrp);
+
+       /* Not a signal, just exit */
+       if (sig <= 0)
+               _exit(- sig);
+
+       kill_myself_with_sig(sig); /* does not return */
+}
+
+/* Restores tty foreground process group, and exits. */
+static void hush_exit(int exitcode) ATTRIBUTE_NORETURN;
+static void hush_exit(int exitcode)
+{
+       fflush(NULL); /* flush all streams */
+       sigexit(- (exitcode & 0xff));
+}
+
+#else /* !JOB */
+
+#define set_fatal_sighandler(handler)   ((void)0)
+#define set_jobctrl_sighandler(handler) ((void)0)
+#define set_misc_sighandler(handler)    ((void)0)
+#define hush_exit(e)                    exit(e)
+
+#endif /* JOB */
+
+
+static const char *set_cwd(void)
+{
+       if (cwd == bb_msg_unknown)
+               cwd = NULL;     /* xrealloc_getcwd_or_warn(arg) calls free(arg)! */
+       cwd = xrealloc_getcwd_or_warn((char *)cwd);
+       if (!cwd)
+               cwd = bb_msg_unknown;
+       return cwd;
+}
+
+
+/* built-in 'test' handler */
+static int builtin_test(char **argv)
+{
+       int argc = 0;
+       while (*argv) {
+               argc++;
+               argv++;
+       }
+       return test_main(argc, argv - argc);
+}
+
+/* built-in 'test' handler */
+static int builtin_echo(char **argv)
+{
+       int argc = 0;
+       while (*argv) {
+               argc++;
+               argv++;
+       }
+       return echo_main(argc, argv - argc);
+}
+
+/* built-in 'eval' handler */
+static int builtin_eval(char **argv)
+{
+       int rcode = EXIT_SUCCESS;
+
+       if (argv[1]) {
+               char *str = expand_strvec_to_string(argv + 1);
+               parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP |
+                                       PARSEFLAG_SEMICOLON);
+               free(str);
+               rcode = last_return_code;
+       }
+       return rcode;
+}
+
+/* built-in 'cd <path>' handler */
+static int builtin_cd(char **argv)
+{
+       const char *newdir;
+       if (argv[1] == 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 = getenv("HOME") ? : "/";
+       } else
+               newdir = argv[1];
+       if (chdir(newdir)) {
+               printf("cd: %s: %s\n", newdir, strerror(errno));
+               return EXIT_FAILURE;
+       }
+       set_cwd();
+       return EXIT_SUCCESS;
+}
+
+/* built-in 'exec' handler */
+static int builtin_exec(char **argv)
+{
+       if (argv[1] == NULL)
+               return EXIT_SUCCESS; /* bash does this */
+// FIXME: if exec fails, bash does NOT exit! We do...
+       pseudo_exec_argv(argv + 1);
+       /* never returns */
+}
+
+/* built-in 'exit' handler */
+static int builtin_exit(char **argv)
+{
+// TODO: bash does it ONLY on top-level sh exit (+interacive only?)
+       //puts("exit"); /* bash does it */
+// TODO: warn if we have background jobs: "There are stopped jobs"
+// On second consecutive 'exit', exit anyway.
+
+       if (argv[1] == NULL)
+               hush_exit(last_return_code);
+       /* mimic bash: exit 123abc == exit 255 + error msg */
+       xfunc_error_retval = 255;
+       /* bash: exit -2 == exit 254, no error msg */
+       hush_exit(xatoi(argv[1]) & 0xff);
+}
+
+/* built-in 'export VAR=value' handler */
+static int builtin_export(char **argv)
+{
+       const char *value;
+       char *name = argv[1];
+
+       if (name == NULL) {
+               // TODO:
+               // ash emits: export VAR='VAL'
+               // bash: declare -x VAR="VAL"
+               // (both also escape as needed (quotes, $, etc))
+               char **e = environ;
+               if (e)
+                       while (*e)
+                               puts(*e++);
+               return EXIT_SUCCESS;
+       }
+
+       value = strchr(name, '=');
+       if (!value) {
+               /* They are exporting something without a =VALUE */
+               struct variable *var;
+
+               var = get_local_var(name);
+               if (var) {
+                       var->flg_export = 1;
+                       putenv(var->varstr);
+               }
+               /* bash does not return an error when trying to export
+                * an undefined variable.  Do likewise. */
+               return EXIT_SUCCESS;
+       }
+
+       set_local_var(xstrdup(name), 1);
+       return EXIT_SUCCESS;
+}
+
+#if ENABLE_HUSH_JOB
+/* built-in 'fg' and 'bg' handler */
+static int builtin_fg_bg(char **argv)
+{
+       int i, jobnum;
+       struct pipe *pi;
+
+       if (!interactive_fd)
+               return EXIT_FAILURE;
+       /* If they gave us no args, assume they want the last backgrounded task */
+       if (!argv[1]) {
+               for (pi = job_list; pi; pi = pi->next) {
+                       if (pi->jobid == 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 = 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] == 'f') {
+               /* Put the job into the foreground.  */
+               tcsetpgrp(interactive_fd, pi->pgrp);
+       }
+
+       /* Restart the processes in the job */
+       debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_progs, pi->pgrp);
+       for (i = 0; i < pi->num_progs; i++) {
+               debug_printf_jobs("reviving pid %d\n", pi->progs[i].pid);
+               pi->progs[i].is_stopped = 0;
+       }
+       pi->stopped_progs = 0;
+
+       i = kill(- pi->pgrp, SIGCONT);
+       if (i < 0) {
+               if (errno == ESRCH) {
+                       delete_finished_bg_job(pi);
+                       return EXIT_SUCCESS;
+               } else {
+                       bb_perror_msg("kill (SIGCONT)");
+               }
+       }
+
+       if (*argv[0] == 'f') {
+               remove_bg_job(pi);
+               return checkjobs_and_fg_shell(pi);
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
+/* built-in 'help' handler */
+#if ENABLE_HUSH_HELP
+static int builtin_help(char **argv ATTRIBUTE_UNUSED)
+{
+       const struct built_in_command *x;
+
+       printf("\nBuilt-in commands:\n");
+       printf("-------------------\n");
+       for (x = bltins; x->cmd; x++) {
+               printf("%s\t%s\n", x->cmd, x->descr);
+       }
+       printf("\n\n");
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_JOB
+/* built-in 'jobs' handler */
+static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
+{
+       struct pipe *job;
+       const char *status_string;
+
+       for (job = job_list; job; job = job->next) {
+               if (job->running_progs == job->stopped_progs)
+                       status_string = "Stopped";
+               else
+                       status_string = "Running";
+
+               printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
+/* built-in 'pwd' handler */
+static int builtin_pwd(char **argv ATTRIBUTE_UNUSED)
+{
+       puts(set_cwd());
+       return EXIT_SUCCESS;
+}
+
+/* built-in 'read VAR' handler */
+static int builtin_read(char **argv)
+{
+       char *string;
+       const char *name = argv[1] ? argv[1] : "REPLY";
+
+       string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name));
+       return set_local_var(string, 0);
+}
+
+/* built-in 'set [VAR=value]' handler */
+static int builtin_set(char **argv)
+{
+       char *temp = argv[1];
+       struct variable *e;
+
+       if (temp == NULL)
+               for (e = top_var; e; e = e->next)
+                       puts(e->varstr);
+       else
+               set_local_var(xstrdup(temp), 0);
+
+       return EXIT_SUCCESS;
+}
+
+
+/* Built-in 'shift' handler */
+static int builtin_shift(char **argv)
+{
+       int n = 1;
+       if (argv[1]) {
+               n = atoi(argv[1]);
+       }
+       if (n >= 0 && n < global_argc) {
+               global_argv[n] = global_argv[0];
+               global_argc -= n;
+               global_argv += n;
+               return EXIT_SUCCESS;
+       }
+       return EXIT_FAILURE;
+}
+
+/* Built-in '.' handler (read-in and execute commands from file) */
+static int builtin_source(char **argv)
+{
+       FILE *input;
+       int status;
+
+       if (argv[1] == NULL)
+               return EXIT_FAILURE;
+
+       /* XXX search through $PATH is missing */
+       input = fopen(argv[1], "r");
+       if (!input) {
+               bb_error_msg("cannot open '%s'", argv[1]);
+               return EXIT_FAILURE;
+       }
+       close_on_exec_on(fileno(input));
+
+       /* Now run the file */
+       /* XXX argv and argc are broken; need to save old global_argv
+        * (pointer only is OK!) on this stack frame,
+        * set global_argv=argv+1, recurse, and restore. */
+       status = parse_and_run_file(input);
+       fclose(input);
+       return status;
+}
+
+static int builtin_umask(char **argv)
+{
+       mode_t new_umask;
+       const char *arg = argv[1];
+       char *end;
+       if (arg) {
+               new_umask = strtoul(arg, &end, 8);
+               if (*end != '\0' || end == arg) {
+                       return EXIT_FAILURE;
+               }
+       } else {
+               new_umask = umask(0);
+               printf("%.3o\n", (unsigned) new_umask);
+       }
+       umask(new_umask);
+       return EXIT_SUCCESS;
+}
+
+/* built-in 'unset VAR' handler */
+static int builtin_unset(char **argv)
+{
+       /* bash always returns true */
+       unset_local_var(argv[1]);
+       return EXIT_SUCCESS;
+}
+
+//static int builtin_not_written(char **argv)
+//{
+//     printf("builtin_%s not written\n", argv[0]);
+//     return EXIT_FAILURE;
+//}
+
+static int b_check_space(o_string *o, int len)
+{
+       /* It would be easy to drop a more restrictive policy
+        * in here, such as setting a maximum string length */
+       if (o->length + len > o->maxlen) {
+               /* assert(data == NULL || o->maxlen != 0); */
+               o->maxlen += (2*len > B_CHUNK ? 2*len : B_CHUNK);
+               o->data = xrealloc(o->data, 1 + o->maxlen);
+       }
+       return o->data == NULL;
+}
+
+static int b_addchr(o_string *o, int ch)
+{
+       debug_printf("b_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o);
+       if (b_check_space(o, 1))
+               return B_NOSPAC;
+       o->data[o->length] = ch;
+       o->length++;
+       o->data[o->length] = '\0';
+       return 0;
+}
+
+static void b_reset(o_string *o)
+{
+       o->length = 0;
+       o->nonnull = 0;
+       if (o->data)
+               o->data[0] = '\0';
+}
+
+static void b_free(o_string *o)
+{
+       free(o->data);
+       memset(o, 0, sizeof(*o));
+}
+
+/* My analysis of quoting semantics tells me that state information
+ * is associated with a destination, not a source.
+ */
+static int b_addqchr(o_string *o, int ch, int quote)
+{
+       if (quote && strchr("*?[\\", ch)) {
+               int rc;
+               rc = b_addchr(o, '\\');
+               if (rc)
+                       return rc;
+       }
+       return b_addchr(o, ch);
+}
+
+static int static_get(struct in_str *i)
+{
+       int ch = *i->p++;
+       if (ch == '\0') return EOF;
+       return ch;
+}
+
+static int static_peek(struct in_str *i)
+{
+       return *i->p;
+}
+
+#if ENABLE_HUSH_INTERACTIVE
+#if ENABLE_FEATURE_EDITING
+static void cmdedit_set_initial_prompt(void)
+{
+#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       PS1 = NULL;
+#else
+       PS1 = getenv("PS1");
+       if (PS1 == NULL)
+               PS1 = "\\w \\$ ";
+#endif
+}
+#endif /* EDITING */
+
+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*)PS1);
+               PS1 = xasprintf("%s %c ", cwd, (geteuid() != 0) ? '$' : '#');
+               prompt_str = PS1;
+       } else {
+               prompt_str = PS2;
+       }
+#else
+       prompt_str = (promptmode == 0) ? PS1 : PS2;
+#endif
+       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 {
+               r = read_line_input(prompt_str, user_input_buf, BUFSIZ-1, line_input_state);
+       } while (r == 0); /* repeat if Ctrl-C */
+       i->eof_flag = (r < 0);
+       if (i->eof_flag) { /* EOF/error detected */
+               user_input_buf[0] = EOF; /* yes, it will be truncated, it's ok */
+               user_input_buf[1] = '\0';
+       }
+#else
+       fputs(prompt_str, stdout);
+       fflush(stdout);
+       user_input_buf[0] = r = fgetc(i->file);
+       /*user_input_buf[1] = '\0'; - already is and never changed */
+       i->eof_flag = (r == EOF);
+#endif
+       i->p = 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;
+       } 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 (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
+               ch = fgetc(i->file);
+       }
+       debug_printf("file_get: got a '%c' %d\n", ch, ch);
+#if ENABLE_HUSH_INTERACTIVE
+       if (ch == '\n')
+               i->promptme = 1;
+#endif
+       return ch;
+}
+
+/* All the callers guarantee this routine will never be
+ * used right after a newline, so prompting is not needed.
+ */
+static int file_peek(struct in_str *i)
+{
+       int ch;
+       if (i->p && *i->p) {
+               if (i->eof_flag && !i->p[1])
+                       return EOF;
+               return *i->p;
+       }
+       ch = fgetc(i->file);
+       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 a '%c' %d\n", *i->p, *i->p);
+       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;
+}
+
+/* squirrel != NULL means we squirrel away copies of stdin, stdout,
+ * and stderr if they are redirected. */
+static int setup_redirects(struct child_prog *prog, int squirrel[])
+{
+       int openfd, mode;
+       struct redir_struct *redir;
+
+       for (redir = prog->redirects; redir; redir = redir->next) {
+               if (redir->dup == -1 && redir->glob_word == NULL) {
+                       /* something went wrong in the parse.  Pretend it didn't happen */
+                       continue;
+               }
+               if (redir->dup == -1) {
+                       char *p;
+                       mode = redir_table[redir->type].mode;
+                       p = expand_string_to_string(redir->glob_word[0]);
+                       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!) */
+                               return 1;
+                       }
+               } else {
+                       openfd = redir->dup;
+               }
+
+               if (openfd != redir->fd) {
+                       if (squirrel && redir->fd < 3) {
+                               squirrel[redir->fd] = dup(redir->fd);
+                       }
+                       if (openfd == -3) {
+                               //close(openfd); // close(-3) ??!
+                       } else {
+                               dup2(openfd, redir->fd);
+                               if (redir->dup == -1)
+                                       close(openfd);
+                       }
+               }
+       }
+       return 0;
+}
+
+static void restore_redirects(int squirrel[])
+{
+       int i, fd;
+       for (i = 0; i < 3; i++) {
+               fd = squirrel[i];
+               if (fd != -1) {
+                       /* We simply die on error */
+                       xmove_fd(fd, i);
+               }
+       }
+}
+
+/* Called after [v]fork() in run_pipe(), or from builtin_exec().
+ * Never returns.
+ * XXX no exit() here.  If you don't exec, use _exit instead.
+ * The at_exit handlers apparently confuse the calling process,
+ * in particular stdin handling.  Not sure why? -- because of vfork! (vda) */
+static void pseudo_exec_argv(char **argv)
+{
+       int i, rcode;
+       char *p;
+       const struct built_in_command *x;
+
+       for (i = 0; is_assignment(argv[i]); i++) {
+               debug_printf_exec("pid %d environment modification: %s\n",
+                               getpid(), argv[i]);
+// FIXME: vfork case??
+               p = expand_string_to_string(argv[i]);
+               putenv(p);
+       }
+       argv += i;
+       /* If a variable is assigned in a forest, and nobody listens,
+        * was it ever really set?
+        */
+       if (!argv[0])
+               _exit(EXIT_SUCCESS);
+
+       argv = expand_strvec_to_strvec(argv);
+
+       /*
+        * Check if the command matches any of the builtins.
+        * Depending on context, this might be redundant.  But it's
+        * easier to waste a few CPU cycles than it is to figure out
+        * if this is one of those cases.
+        */
+       for (x = bltins; x->cmd; x++) {
+               if (strcmp(argv[0], x->cmd) == 0) {
+                       debug_printf_exec("running builtin '%s'\n", argv[0]);
+                       rcode = x->function(argv);
+                       fflush(stdout);
+                       _exit(rcode);
+               }
+       }
+
+       /* Check if the command matches any busybox applets */
+#if ENABLE_FEATURE_SH_STANDALONE
+       if (strchr(argv[0], '/') == NULL) {
+               int a = find_applet_by_name(argv[0]);
+               if (a >= 0) {
+                       if (APPLET_IS_NOEXEC(a)) {
+                               debug_printf_exec("running applet '%s'\n", argv[0]);
+// is it ok that run_applet_no_and_exit() does exit(), not _exit()?
+                               run_applet_no_and_exit(a, argv);
+                       }
+                       /* re-exec ourselves with the new arguments */
+                       debug_printf_exec("re-execing applet '%s'\n", argv[0]);
+                       execvp(bb_busybox_exec_path, argv);
+                       /* If they called chroot or otherwise made the binary no longer
+                        * executable, fall through */
+               }
+       }
+#endif
+
+       debug_printf_exec("execing '%s'\n", argv[0]);
+       execvp(argv[0], argv);
+       bb_perror_msg("cannot exec '%s'", argv[0]);
+       _exit(1);
+}
+
+/* Called after [v]fork() in run_pipe()
+ */
+static void pseudo_exec(struct child_prog *child)
+{
+// FIXME: buggy wrt NOMMU! Must not modify any global data
+// until it does exec/_exit, but currently it does
+// (puts malloc'ed stuff into environment)
+       if (child->argv)
+               pseudo_exec_argv(child->argv);
+
+       if (child->group) {
+#if !BB_MMU
+               bb_error_msg_and_die("nested lists are not supported on NOMMU");
+#else
+               int rcode;
+
+#if ENABLE_HUSH_INTERACTIVE
+// run_list_level now takes care of it?
+//             debug_printf_exec("pseudo_exec: setting interactive_fd=0\n");
+//             interactive_fd = 0;    /* crucial!!!! */
+#endif
+               debug_printf_exec("pseudo_exec: run_list\n");
+               rcode = run_list(child->group);
+               /* OK to leak memory by not calling free_pipe_list,
+                * since this process is about to exit */
+               _exit(rcode);
+#endif
+       }
+
+       /* Can happen.  See what bash does with ">foo" by itself. */
+       debug_printf("trying to pseudo_exec 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->progs[0].argv;
+       if (!argv || !argv[0])
+               return (pi->cmdtext = xzalloc(1));
+
+       len = 0;
+       do len += strlen(*argv) + 1; while (*++argv);
+       pi->cmdtext = p = xmalloc(len);
+       argv = pi->progs[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 *thejob;
+       int i;
+
+       /* Linear search for the ID of the job to use */
+       pi->jobid = 1;
+       for (thejob = job_list; thejob; thejob = thejob->next)
+               if (thejob->jobid >= pi->jobid)
+                       pi->jobid = thejob->jobid + 1;
+
+       /* Add thejob to the list of running jobs */
+       if (!job_list) {
+               thejob = job_list = xmalloc(sizeof(*thejob));
+       } else {
+               for (thejob = job_list; thejob->next; thejob = thejob->next)
+                       continue;
+               thejob->next = xmalloc(sizeof(*thejob));
+               thejob = thejob->next;
+       }
+
+       /* Physically copy the struct job */
+       memcpy(thejob, pi, sizeof(struct pipe));
+       thejob->progs = xzalloc(sizeof(pi->progs[0]) * pi->num_progs);
+       /* We cannot copy entire pi->progs[] vector! Double free()s will happen */
+       for (i = 0; i < pi->num_progs; i++) {
+// TODO: do we really need to have so many fields which are just dead weight
+// at execution stage?
+               thejob->progs[i].pid = pi->progs[i].pid;
+               /* all other fields are not used and stay zero */
+       }
+       thejob->next = NULL;
+       thejob->cmdtext = xstrdup(get_cmdtext(pi));
+
+       /* We don't wait for background thejobs to return -- append it
+          to the list of backgrounded thejobs and leave it alone */
+       printf("[%d] %d %s\n", thejob->jobid, thejob->progs[0].pid, thejob->cmdtext);
+       last_bg_pid = thejob->progs[0].pid;
+       last_jobid = thejob->jobid;
+}
+
+static void remove_bg_job(struct pipe *pi)
+{
+       struct pipe *prev_pipe;
+
+       if (pi == job_list) {
+               job_list = pi->next;
+       } else {
+               prev_pipe = job_list;
+               while (prev_pipe->next != pi)
+                       prev_pipe = prev_pipe->next;
+               prev_pipe->next = pi->next;
+       }
+       if (job_list)
+               last_jobid = job_list->jobid;
+       else
+               last_jobid = 0;
+}
+
+/* remove a backgrounded job */
+static void delete_finished_bg_job(struct pipe *pi)
+{
+       remove_bg_job(pi);
+       pi->stopped_progs = 0;
+       free_pipe(pi, 0);
+       free(pi);
+}
+#endif /* JOB */
+
+/* Checks to see if any processes have exited -- if they
+   have, figure out why and see if a job has completed */
+static int checkjobs(struct pipe* fg_pipe)
+{
+       int attributes;
+       int status;
+#if ENABLE_HUSH_JOB
+       int prognum = 0;
+       struct pipe *pi;
+#endif
+       pid_t childpid;
+       int rcode = 0;
+
+       attributes = WUNTRACED;
+       if (fg_pipe == NULL) {
+               attributes |= WNOHANG;
+       }
+
+/* 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!
+ */
+
+//FIXME: non-interactive bash does not continue even if all processes in fg pipe
+//are stopped. Testcase: "cat | cat" in a script (not on command line)
+// + killall -STOP cat
+
+ wait_more:
+// TODO: safe_waitpid?
+       while ((childpid = waitpid(-1, &status, attributes)) > 0) {
+               const int dead = WIFEXITED(status) || WIFSIGNALED(status);
+
+#ifdef DEBUG_SHELL_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) {
+                       int i;
+                       for (i = 0; i < fg_pipe->num_progs; i++) {
+                               debug_printf_jobs("check pid %d\n", fg_pipe->progs[i].pid);
+                               if (fg_pipe->progs[i].pid == childpid) {
+                                       /* printf("process %d exit %d\n", i, WEXITSTATUS(status)); */
+                                       if (dead) {
+                                               fg_pipe->progs[i].pid = 0;
+                                               fg_pipe->running_progs--;
+                                               if (i == fg_pipe->num_progs - 1)
+                                                       /* last process gives overall exitstatus */
+                                                       rcode = WEXITSTATUS(status);
+                                       } else {
+                                               fg_pipe->progs[i].is_stopped = 1;
+                                               fg_pipe->stopped_progs++;
+                                       }
+                                       debug_printf_jobs("fg_pipe: running_progs %d stopped_progs %d\n",
+                                                       fg_pipe->running_progs, fg_pipe->stopped_progs);
+                                       if (fg_pipe->running_progs - fg_pipe->stopped_progs <= 0) {
+                                               /* All processes in fg pipe have exited/stopped */
+#if ENABLE_HUSH_JOB
+                                               if (fg_pipe->running_progs)
+                                                       insert_bg_job(fg_pipe);
+#endif
+                                               return rcode;
+                                       }
+                                       /* There are still running processes in the fg pipe */
+                                       goto wait_more;
+                               }
+                       }
+                       /* fall through to searching 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 = job_list; pi; pi = pi->next) {
+                       prognum = 0;
+                       while (prognum < pi->num_progs) {
+                               if (pi->progs[prognum].pid == childpid)
+                                       goto found_pi_and_prognum;
+                               prognum++;
+                       }
+               }
+#endif
+
+               /* Happens when shell is used as init process (init=/bin/sh) */
+               debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
+               goto wait_more;
+
+#if ENABLE_HUSH_JOB
+ found_pi_and_prognum:
+               if (dead) {
+                       /* child exited */
+                       pi->progs[prognum].pid = 0;
+                       pi->running_progs--;
+                       if (!pi->running_progs) {
+                               printf(JOB_STATUS_FORMAT, pi->jobid,
+                                                       "Done", pi->cmdtext);
+                               delete_finished_bg_job(pi);
+                       }
+               } else {
+                       /* child stopped */
+                       pi->stopped_progs++;
+                       pi->progs[prognum].is_stopped = 1;
+               }
+#endif
+       }
+
+       /* wait found no children or failed */
+
+       if (childpid && errno != ECHILD)
+               bb_perror_msg("waitpid");
+       return rcode;
+}
+
+#if ENABLE_HUSH_JOB
+static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
+{
+       pid_t p;
+       int rcode = checkjobs(fg_pipe);
+       /* Job finished, move the shell to the foreground */
+       p = getpgid(0); /* pgid of our process */
+       debug_printf_jobs("fg'ing ourself: getpgid(0)=%d\n", (int)p);
+       if (tcsetpgrp(interactive_fd, p) && errno != ENOTTY)
+               bb_perror_msg("tcsetpgrp-4a");
+       return rcode;
+}
+#endif
+
+/* run_pipe() starts all the jobs, but doesn't wait for anything
+ * to finish.  See checkjobs().
+ *
+ * return code is normally -1, when the caller has to wait for children
+ * to finish to determine the exit status of the pipe.  If the pipe
+ * is a simple builtin command, however, the action is done by the
+ * time run_pipe returns, and the exit code is provided as the
+ * return value.
+ *
+ * The input of the pipe is always stdin, the output is always
+ * stdout.  The outpipe[] mechanism in BusyBox-0.48 lash is bogus,
+ * because it tries to avoid running the command substitution in
+ * subshell, when that is in fact necessary.  The subshell process
+ * now has its stdout directed to the input of the appropriate pipe,
+ * so this routine is noticeably simpler.
+ *
+ * Returns -1 only if started some children. IOW: we have to
+ * mask out retvals of builtins etc with 0xff!
+ */
+static int run_pipe(struct pipe *pi)
+{
+       int i;
+       int nextin;
+       int pipefds[2];         /* pipefds[0] is for reading */
+       struct child_prog *child;
+       const struct built_in_command *x;
+       char *p;
+       /* it is not always needed, but we aim to smaller code */
+       int squirrel[] = { -1, -1, -1 };
+       int rcode;
+       const int single_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG);
+
+       debug_printf_exec("run_pipe start: single_fg=%d\n", single_fg);
+
+#if ENABLE_HUSH_JOB
+       pi->pgrp = -1;
+#endif
+       pi->running_progs = 1;
+       pi->stopped_progs = 0;
+
+       /* Check if this is a simple builtin (not part of a pipe).
+        * Builtins within pipes have to fork anyway, and are handled in
+        * pseudo_exec.  "echo foo | read bar" doesn't work on bash, either.
+        */
+       child = &(pi->progs[0]);
+       if (single_fg && child->group && child->subshell == 0) {
+               debug_printf("non-subshell grouping\n");
+               setup_redirects(child, squirrel);
+               debug_printf_exec(": run_list\n");
+               rcode = run_list(child->group) & 0xff;
+               restore_redirects(squirrel);
+               debug_printf_exec("run_pipe return %d\n", rcode);
+               return rcode;
+       }
+
+       if (single_fg && child->argv != NULL) {
+               char **argv_expanded;
+               char **argv = child->argv;
+
+               for (i = 0; is_assignment(argv[i]); i++)
+                       continue;
+               if (i != 0 && argv[i] == NULL) {
+                       /* assignments, but no command: set the local environment */
+                       for (i = 0; argv[i] != NULL; i++) {
+                               debug_printf("local environment set: %s\n", argv[i]);
+                               p = expand_string_to_string(argv[i]);
+                               set_local_var(p, 0);
+                       }
+                       return EXIT_SUCCESS;   /* don't worry about errors in set_local_var() yet */
+               }
+               for (i = 0; is_assignment(argv[i]); i++) {
+                       p = expand_string_to_string(argv[i]);
+                       //sp: child->sp--;
+                       putenv(p);
+               }
+               for (x = bltins; x->cmd; x++) {
+                       if (strcmp(argv[i], x->cmd) == 0) {
+                               if (x->function == builtin_exec && argv[i+1] == NULL) {
+                                       debug_printf("magic exec\n");
+                                       setup_redirects(child, NULL);
+                                       return EXIT_SUCCESS;
+                               }
+                               debug_printf("builtin inline %s\n", argv[0]);
+                               /* XXX setup_redirects acts on file descriptors, not FILEs.
+                                * This is perfect for work that comes after exec().
+                                * Is it really safe for inline use?  Experimentally,
+                                * things seem to work with glibc. */
+                               setup_redirects(child, squirrel);
+                               debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]);
+                               //sp: if (child->sp) /* btw we can do it unconditionally... */
+                               argv_expanded = expand_strvec_to_strvec(argv + i);
+                               rcode = x->function(argv_expanded) & 0xff;
+                               free(argv_expanded);
+                               restore_redirects(squirrel);
+                               debug_printf_exec("run_pipe return %d\n", rcode);
+                               return rcode;
+                       }
+               }
+#if ENABLE_FEATURE_SH_STANDALONE
+               {
+                       int a = find_applet_by_name(argv[i]);
+                       if (a >= 0 && APPLET_IS_NOFORK(a)) {
+                               setup_redirects(child, squirrel);
+                               save_nofork_data(&nofork_save);
+                               argv_expanded = argv + i;
+                               //sp: if (child->sp)
+                               argv_expanded = expand_strvec_to_strvec(argv + i);
+                               debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]);
+                               rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded) & 0xff;
+                               free(argv_expanded);
+                               restore_redirects(squirrel);
+                               debug_printf_exec("run_pipe return %d\n", rcode);
+                               return rcode;
+                       }
+               }
+#endif
+       }
+
+       /* Disable job control signals for shell (parent) and
+        * for initial child code after fork */
+       set_jobctrl_sighandler(SIG_IGN);
+
+       /* Going to fork a child per each pipe member */
+       pi->running_progs = 0;
+       nextin = 0;
+
+       for (i = 0; i < pi->num_progs; i++) {
+               child = &(pi->progs[i]);
+               if (child->argv)
+                       debug_printf_exec(": pipe member '%s' '%s'...\n", child->argv[0], child->argv[1]);
+               else
+                       debug_printf_exec(": pipe member with no argv\n");
+
+               /* pipes are inserted between pairs of commands */
+               pipefds[0] = 0;
+               pipefds[1] = 1;
+               if ((i + 1) < pi->num_progs)
+                       xpipe(pipefds);
+
+               child->pid = BB_MMU ? fork() : vfork();
+               if (!child->pid) { /* child */
+                       if (ENABLE_HUSH_JOB)
+                               die_sleep = 0; /* let nofork's xfuncs die */
+#if ENABLE_HUSH_JOB
+                       /* Every child adds itself to new process group
+                        * with pgid == pid_of_first_child_in_pipe */
+                       if (run_list_level == 1 && interactive_fd) {
+                               pid_t pgrp;
+                               /* Don't do pgrp restore anymore on fatal signals */
+                               set_fatal_sighandler(SIG_DFL);
+                               pgrp = pi->pgrp;
+                               if (pgrp < 0) /* true for 1st process only */
+                                       pgrp = getpid();
+                               if (setpgid(0, pgrp) == 0 && pi->followup != PIPE_BG) {
+                                       /* We do it in *every* child, not just first,
+                                        * to avoid races */
+                                       tcsetpgrp(interactive_fd, pgrp);
+                               }
+                       }
+#endif
+                       xmove_fd(nextin, 0);
+                       xmove_fd(pipefds[1], 1); /* write end */
+                       if (pipefds[0] > 1)
+                               close(pipefds[0]); /* read end */
+                       /* Like bash, explicit redirects override pipes,
+                        * and the pipe fd is available for dup'ing. */
+                       setup_redirects(child, NULL);
+
+                       /* Restore default handlers just prior to exec */
+                       set_jobctrl_sighandler(SIG_DFL);
+                       set_misc_sighandler(SIG_DFL);
+                       signal(SIGCHLD, SIG_DFL);
+                       pseudo_exec(child); /* does not return */
+               }
+
+               if (child->pid < 0) { /* [v]fork failed */
+                       /* Clearly indicate, was it fork or vfork */
+                       bb_perror_msg(BB_MMU ? "fork" : "vfork");
+               } else {
+                       pi->running_progs++;
+#if ENABLE_HUSH_JOB
+                       /* Second and next children need to know pid of first one */
+                       if (pi->pgrp < 0)
+                               pi->pgrp = child->pid;
+#endif
+               }
+
+               if (i)
+                       close(nextin);
+               if ((i + 1) < pi->num_progs)
+                       close(pipefds[1]); /* write end */
+               /* Pass read (output) pipe end to next iteration */
+               nextin = pipefds[0];
+       }
+
+       if (!pi->running_progs) {
+               debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n");
+               return 1;
+       }
+
+       debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->running_progs);
+       return -1;
+}
+
+#ifndef debug_print_tree
+static void debug_print_tree(struct pipe *pi, int lvl)
+{
+       static const char *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" ,
+               [RES_IN   ] = "IN"   ,
+#endif
+               [RES_XXXX ] = "XXXX" ,
+               [RES_SNTX ] = "SNTX" ,
+       };
+
+       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_progs) {
+                       struct child_prog *child = &pi->progs[prn];
+                       char **argv = child->argv;
+
+                       fprintf(stderr, "%*s prog %d", lvl*2, "", prn);
+                       if (child->group) {
+                               fprintf(stderr, " group %s: (argv=%p)\n",
+                                               (child->subshell ? "()" : "{}"),
+                                               argv);
+                               debug_print_tree(child->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)
+{
+       struct pipe *rpipe;
+#if ENABLE_HUSH_LOOPS
+       char *for_varname = NULL;
+       char **for_lcur = NULL;
+       char **for_list = NULL;
+       int flag_rep = 0;
+#endif
+       int flag_skip = 1;
+       int rcode = 0; /* probably for gcc only */
+       int flag_restore = 0;
+#if ENABLE_HUSH_IF
+       int if_code = 0, next_if_code = 0;  /* need double-buffer to handle elif */
+#else
+       enum { if_code = 0, next_if_code = 0 };
+#endif
+       reserved_style rword;
+       reserved_style skip_more_for_this_rword = RES_XXXX;
+
+       debug_printf_exec("run_list start lvl %d\n", run_list_level + 1);
+
+#if ENABLE_HUSH_LOOPS
+       /* check syntax for "for" */
+       for (rpipe = pi; rpipe; rpipe = rpipe->next) {
+               if ((rpipe->res_word == RES_IN || rpipe->res_word == RES_FOR)
+                && (rpipe->next == NULL)
+               ) {
+                       syntax("malformed for"); /* no IN or no commands after IN */
+                       debug_printf_exec("run_list lvl %d return 1\n", run_list_level);
+                       return 1;
+               }
+               if ((rpipe->res_word == RES_IN && rpipe->next->res_word == RES_IN && rpipe->next->progs[0].argv != NULL)
+                || (rpipe->res_word == RES_FOR && rpipe->next->res_word != RES_IN)
+               ) {
+                       /* TODO: what is tested in the first condition? */
+                       syntax("malformed for"); /* 2nd condition: not followed by IN */
+                       debug_printf_exec("run_list lvl %d return 1\n", run_list_level);
+                       return 1;
+               }
+       }
+#else
+       rpipe = NULL;
+#endif
+
+#if ENABLE_HUSH_JOB
+       /* Example of nested list: "while true; do { sleep 1 | exit 2; } done".
+        * We are saving state before entering outermost list ("while...done")
+        * so that ctrl-Z will correctly background _entire_ outermost list,
+        * not just a part of it (like "sleep 1 | exit 2") */
+       if (++run_list_level == 1 && interactive_fd) {
+               if (sigsetjmp(toplevel_jb, 1)) {
+                       /* ctrl-Z forked and we are parent; or ctrl-C.
+                        * Sighandler has longjmped us here */
+                       signal(SIGINT, SIG_IGN);
+                       signal(SIGTSTP, SIG_IGN);
+                       /* Restore level (we can be coming from deep inside
+                        * nested levels) */
+                       run_list_level = 1;
+#if ENABLE_FEATURE_SH_STANDALONE
+                       if (nofork_save.saved) { /* if save area is valid */
+                               debug_printf_jobs("exiting nofork early\n");
+                               restore_nofork_data(&nofork_save);
+                       }
+#endif
+                       if (ctrl_z_flag) {
+                               /* ctrl-Z has forked and stored pid of the child in pi->pid.
+                                * Remember this child as background job */
+                               insert_bg_job(pi);
+                       } else {
+                               /* ctrl-C. We just stop doing whatever we were doing */
+                               bb_putchar('\n');
+                       }
+                       rcode = 0;
+                       goto ret;
+               }
+               /* ctrl-Z handler will store pid etc in pi */
+               toplevel_list = pi;
+               ctrl_z_flag = 0;
+#if ENABLE_FEATURE_SH_STANDALONE
+               nofork_save.saved = 0; /* in case we will run a nofork later */
+#endif
+               signal_SA_RESTART_empty_mask(SIGTSTP, handler_ctrl_z);
+               signal(SIGINT, handler_ctrl_c);
+       }
+#endif /* JOB */
+
+       for (; pi; pi = flag_restore ? rpipe : pi->next) {
+//why?         int save_num_progs;
+               rword = pi->res_word;
+#if ENABLE_HUSH_LOOPS
+               if (rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) {
+                       flag_restore = 0;
+                       if (!rpipe) {
+                               flag_rep = 0;
+                               rpipe = pi;
+                       }
+               }
+#endif
+               debug_printf_exec(": rword=%d if_code=%d next_if_code=%d skip_more=%d\n",
+                               rword, if_code, next_if_code, skip_more_for_this_rword);
+               if (rword == skip_more_for_this_rword && flag_skip) {
+                       if (pi->followup == PIPE_SEQ)
+                               flag_skip = 0;
+                       continue;
+               }
+               flag_skip = 1;
+               skip_more_for_this_rword = RES_XXXX;
+#if ENABLE_HUSH_IF
+               if (rword == RES_THEN || rword == RES_ELSE)
+                       if_code = next_if_code;
+               if (rword == RES_THEN && if_code)
+                       continue;
+               if (rword == RES_ELSE && !if_code)
+                       continue;
+               if (rword == RES_ELIF && !if_code)
+                       break;
+#endif
+#if ENABLE_HUSH_LOOPS
+               if (rword == RES_FOR && pi->num_progs) {
+                       if (!for_lcur) {
+                               /* first loop through for */
+                               /* if no variable values after "in" we skip "for" */
+                               if (!pi->next->progs->argv)
+                                       continue;
+                               /* create list of variable values */
+                               for_list = expand_strvec_to_strvec(pi->next->progs->argv);
+                               for_lcur = for_list;
+                               for_varname = pi->progs->argv[0];
+                               pi->progs->argv[0] = NULL;
+                               flag_rep = 1;
+                       }
+                       free(pi->progs->argv[0]);
+                       if (!*for_lcur) {
+                               /* for loop is over, clean up */
+                               free(for_list);
+                               for_lcur = NULL;
+                               flag_rep = 0;
+                               pi->progs->argv[0] = for_varname;
+                               continue;
+                       }
+                       /* insert next value from for_lcur */
+                       /* vda: does it need escaping? */
+                       pi->progs->argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
+               }
+               if (rword == RES_IN)
+                       continue;
+               if (rword == RES_DO) {
+                       if (!flag_rep)
+                               continue;
+               }
+               if (rword == RES_DONE) {
+                       if (flag_rep) {
+                               flag_restore = 1;
+                       } else {
+                               rpipe = NULL;
+                       }
+               }
+#endif
+               if (pi->num_progs == 0)
+                       continue;
+//why?         save_num_progs = pi->num_progs;
+               debug_printf_exec(": run_pipe with %d members\n", pi->num_progs);
+               rcode = run_pipe(pi);
+               if (rcode != -1) {
+                       /* We only ran a builtin: rcode was set by the return value
+                        * of run_pipe(), and we don't need to wait for anything. */
+               } 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 */
+#if ENABLE_HUSH_JOB
+                       if (run_list_level == 1)
+                               insert_bg_job(pi);
+#endif
+                       rcode = EXIT_SUCCESS;
+               } else {
+#if ENABLE_HUSH_JOB
+                       if (run_list_level == 1 && interactive_fd) {
+                               /* waits for completion, then fg's main shell */
+                               rcode = checkjobs_and_fg_shell(pi);
+                       } else
+#endif
+                       {
+                               /* this one just waits for completion */
+                               rcode = checkjobs(pi);
+                       }
+                       debug_printf_exec(": checkjobs returned %d\n", rcode);
+               }
+               debug_printf_exec(": setting last_return_code=%d\n", rcode);
+               last_return_code = rcode;
+//why?         pi->num_progs = save_num_progs;
+#if ENABLE_HUSH_IF
+               if (rword == RES_IF || rword == RES_ELIF)
+                       next_if_code = rcode;  /* can be overwritten a number of times */
+#endif
+#if ENABLE_HUSH_LOOPS
+               if (rword == RES_WHILE)
+                       flag_rep = !last_return_code;
+               if (rword == RES_UNTIL)
+                       flag_rep = last_return_code;
+#endif
+               if ((rcode == EXIT_SUCCESS && pi->followup == PIPE_OR)
+                || (rcode != EXIT_SUCCESS && pi->followup == PIPE_AND)
+               ) {
+                       skip_more_for_this_rword = rword;
+               }
+               checkjobs(NULL);
+       }
+
+#if ENABLE_HUSH_JOB
+       if (ctrl_z_flag) {
+               /* ctrl-Z forked somewhere in the past, we are the child,
+                * and now we completed running the list. Exit. */
+               exit(rcode);
+       }
+ ret:
+       if (!--run_list_level && interactive_fd) {
+               signal(SIGTSTP, SIG_IGN);
+               signal(SIGINT, SIG_IGN);
+       }
+#endif
+       debug_printf_exec("run_list lvl %d return %d\n", run_list_level + 1, rcode);
+       return rcode;
+}
+
+/* return code is the exit status of the pipe */
+static int free_pipe(struct pipe *pi, int indent)
+{
+       char **p;
+       struct child_prog *child;
+       struct redir_struct *r, *rnext;
+       int a, i, ret_code = 0;
+
+       if (pi->stopped_progs > 0)
+               return ret_code;
+       debug_printf_clean("%s run pipe: (pid %d)\n", indenter(indent), getpid());
+       for (i = 0; i < pi->num_progs; i++) {
+               child = &pi->progs[i];
+               debug_printf_clean("%s  command %d:\n", indenter(indent), i);
+               if (child->argv) {
+                       for (a = 0, p = child->argv; *p; a++, p++) {
+                               debug_printf_clean("%s   argv[%d] = %s\n", indenter(indent), a, *p);
+                       }
+                       free_strings(child->argv);
+                       child->argv = NULL;
+               } else if (child->group) {
+                       debug_printf_clean("%s   begin group (subshell:%d)\n", indenter(indent), child->subshell);
+                       ret_code = free_pipe_list(child->group, indent+3);
+                       debug_printf_clean("%s   end group\n", indenter(indent));
+               } else {
+                       debug_printf_clean("%s   (nil)\n", indenter(indent));
+               }
+               for (r = child->redirects; r; r = rnext) {
+                       debug_printf_clean("%s   redirect %d%s", indenter(indent), r->fd, redir_table[r->type].descrip);
+                       if (r->dup == -1) {
+                               /* guard against the case >$FOO, where foo is unset or blank */
+                               if (r->glob_word) {
+                                       debug_printf_clean(" %s\n", r->glob_word[0]);
+                                       free_strings(r->glob_word);
+                                       r->glob_word = NULL;
+                               }
+                       } else {
+                               debug_printf_clean("&%d\n", r->dup);
+                       }
+                       rnext = r->next;
+                       free(r);
+               }
+               child->redirects = NULL;
+       }
+       free(pi->progs);   /* children are an array, they get freed all at once */
+       pi->progs = NULL;
+#if ENABLE_HUSH_JOB
+       free(pi->cmdtext);
+       pi->cmdtext = NULL;
+#endif
+       return ret_code;
+}
+
+static int free_pipe_list(struct pipe *head, int indent)
+{
+       int rcode = 0;   /* if list has no members */
+       struct pipe *pi, *next;
+
+       for (pi = head; pi; pi = next) {
+               debug_printf_clean("%s pipe reserved mode %d\n", indenter(indent), pi->res_word);
+               rcode = free_pipe(pi, indent);
+               debug_printf_clean("%s pipe followup code %d\n", indenter(indent), pi->followup);
+               next = pi->next;
+               /*pi->next = NULL;*/
+               free(pi);
+       }
+       return rcode;
+}
+
+/* Select which version we will use */
+static int run_and_free_list(struct pipe *pi)
+{
+       int rcode = 0;
+       debug_printf_exec("run_and_free_list entered\n");
+       if (!fake_mode) {
+               debug_printf_exec(": run_list with %d members\n", pi->num_progs);
+               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, /* indent: */ 0);
+       debug_printf_exec("run_nad_free_list return %d\n", rcode);
+       return rcode;
+}
+
+/* Whoever decided to muck with glob internal data is AN IDIOT! */
+/* uclibc happily changed the way it works (and it has rights to do so!),
+   all hell broke loose (SEGVs) */
+
+/* The API for glob is arguably broken.  This routine pushes a non-matching
+ * string into the output structure, removing non-backslashed backslashes.
+ * If someone can prove me wrong, by performing this function within the
+ * original glob(3) api, feel free to rewrite this routine into oblivion.
+ * XXX broken if the last character is '\\', check that before calling.
+ */
+static char **globhack(const char *src, char **strings)
+{
+       int cnt;
+       const char *s;
+       char *v, *dest;
+
+       for (cnt = 1, s = src; s && *s; s++) {
+               if (*s == '\\') s++;
+               cnt++;
+       }
+       v = dest = xmalloc(cnt);
+       for (s = src; s && *s; s++, dest++) {
+               if (*s == '\\') s++;
+               *dest = *s;
+       }
+       *dest = '\0';
+
+       return add_string_to_strings(strings, v);
+}
+
+/* XXX broken if the last character is '\\', check that before calling */
+static int glob_needed(const char *s)
+{
+       for (; *s; s++) {
+               if (*s == '\\')
+                       s++;
+               if (strchr("*[?", *s))
+                       return 1;
+       }
+       return 0;
+}
+
+static int xglob(o_string *dest, char ***pglob)
+{
+       /* short-circuit for null word */
+       /* we can code this better when the debug_printf's are gone */
+       if (dest->length == 0) {
+               if (dest->nonnull) {
+                       /* bash man page calls this an "explicit" null */
+                       *pglob = globhack(dest->data, *pglob);
+               }
+               return 0;
+       }
+
+       if (glob_needed(dest->data)) {
+               glob_t globdata;
+               int gr;
+
+               memset(&globdata, 0, sizeof(globdata));
+               gr = glob(dest->data, 0, NULL, &globdata);
+               debug_printf("glob returned %d\n", gr);
+               if (gr == GLOB_NOSPACE)
+                       bb_error_msg_and_die("out of memory during glob");
+               if (gr == GLOB_NOMATCH) {
+                       debug_printf("globhack returned %d\n", gr);
+                       /* quote removal, or more accurately, backslash removal */
+                       *pglob = globhack(dest->data, *pglob);
+                       globfree(&globdata);
+                       return 0;
+               }
+               if (gr != 0) { /* GLOB_ABORTED ? */
+                       bb_error_msg("glob(3) error %d", gr);
+               }
+               if (globdata.gl_pathv && globdata.gl_pathv[0])
+                       *pglob = add_strings_to_strings(1, *pglob, globdata.gl_pathv);
+               globfree(&globdata);
+               return gr;
+       }
+
+       *pglob = globhack(dest->data, *pglob);
+       return 0;
+}
+
+/* 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). */
+
+/* Helpers first:
+ * count_XXX estimates size of the block we need. It's okay
+ * to over-estimate sizes a bit, if it makes code simpler */
+static int count_ifs(const char *str)
+{
+       int cnt = 0;
+       debug_printf_expand("count_ifs('%s') ifs='%s'", str, ifs);
+       while (1) {
+               str += strcspn(str, ifs);
+               if (!*str) break;
+               str++; /* str += strspn(str, ifs); */
+               cnt++; /* cnt += strspn(str, ifs); - but this code is larger */
+       }
+       debug_printf_expand(" return %d\n", cnt);
+       return cnt;
+}
+
+static void count_var_expansion_space(int *countp, int *lenp, char *arg)
+{
+       char first_ch;
+       int i;
+       int len = *lenp;
+       int count = *countp;
+       const char *val;
+       char *p;
+
+       while ((p = strchr(arg, SPECIAL_VAR_SYMBOL))) {
+               len += p - arg;
+               arg = ++p;
+               p = strchr(p, SPECIAL_VAR_SYMBOL);
+               first_ch = arg[0];
+
+               switch (first_ch & 0x7f) {
+               /* high bit in 1st_ch indicates that var is double-quoted */
+               case '$': /* pid */
+               case '!': /* bg pid */
+               case '?': /* exitcode */
+               case '#': /* argc */
+                       len += sizeof(int)*3 + 1; /* enough for int */
+                       break;
+               case '*':
+               case '@':
+                       for (i = 1; global_argv[i]; i++) {
+                               len += strlen(global_argv[i]) + 1;
+                               count++;
+                               if (!(first_ch & 0x80))
+                                       count += count_ifs(global_argv[i]);
+                       }
+                       break;
+               default:
+                       *p = '\0';
+                       arg[0] = first_ch & 0x7f;
+                       if (isdigit(arg[0])) {
+                               i = xatoi_u(arg);
+                               val = NULL;
+                               if (i < global_argc)
+                                       val = global_argv[i];
+                       } else
+                               val = lookup_param(arg);
+                       arg[0] = first_ch;
+                       *p = SPECIAL_VAR_SYMBOL;
+
+                       if (val) {
+                               len += strlen(val) + 1;
+                               if (!(first_ch & 0x80))
+                                       count += count_ifs(val);
+                       }
+               }
+               arg = ++p;
+       }
+
+       len += strlen(arg) + 1;
+       count++;
+       *lenp = len;
+       *countp = count;
+}
+
+/* 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(char **list, int n, char **posp, const char *str)
+{
+       char *pos = *posp;
+       while (1) {
+               int word_len = strcspn(str, ifs);
+               if (word_len) {
+                       memcpy(pos, str, word_len); /* store non-ifs chars */
+                       pos += word_len;
+                       str += word_len;
+               }
+               if (!*str)  /* EOL - do not finalize word */
+                       break;
+               *pos++ = '\0';
+               if (n) debug_printf_expand("expand_on_ifs finalized list[%d]=%p '%s' "
+                       "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+                       strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+               list[n++] = pos;
+               str += strspn(str, ifs); /* skip ifs chars */
+       }
+       *posp = pos;
+       return n;
+}
+
+/* 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! */
+/* NB: another bug is that we cannot detect empty strings yet:
+ * "" or $empty"" expands to zero words, has to expand to empty word */
+static int expand_vars_to_list(char **list, int n, char **posp, char *arg, char or_mask)
+{
+       /* or_mask is either 0 (normal case) or 0x80
+        * (expansion of right-hand side of assignment == 1-element expand) */
+
+       char first_ch, ored_ch;
+       int i;
+       const char *val;
+       char *p;
+       char *pos = *posp;
+
+       ored_ch = 0;
+
+       if (n) debug_printf_expand("expand_vars_to_list finalized list[%d]=%p '%s' "
+               "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+               strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+       list[n++] = pos;
+
+       while ((p = strchr(arg, SPECIAL_VAR_SYMBOL))) {
+               memcpy(pos, arg, p - arg);
+               pos += (p - arg);
+               arg = ++p;
+               p = strchr(p, SPECIAL_VAR_SYMBOL);
+
+               first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
+               ored_ch |= first_ch;
+               val = NULL;
+               switch (first_ch & 0x7f) {
+               /* Highest bit in first_ch indicates that var is double-quoted */
+               case '$': /* pid */
+                       /* FIXME: (echo $$) should still print pid of main shell */
+                       val = utoa(getpid()); /* rootpid? */
+                       break;
+               case '!': /* bg pid */
+                       val = last_bg_pid ? utoa(last_bg_pid) : (char*)"";
+                       break;
+               case '?': /* exitcode */
+                       val = utoa(last_return_code);
+                       break;
+               case '#': /* argc */
+                       val = utoa(global_argc ? global_argc-1 : 0);
+                       break;
+               case '*':
+               case '@':
+                       i = 1;
+                       if (!global_argv[i])
+                               break;
+                       if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
+                               while (global_argv[i]) {
+                                       n = expand_on_ifs(list, n, &pos, global_argv[i]);
+                                       debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, global_argc-1);
+                                       if (global_argv[i++][0] && global_argv[i]) {
+                                               /* this argv[] is not empty and not last:
+                                                * put terminating NUL, start new word */
+                                               *pos++ = '\0';
+                                               if (n) debug_printf_expand("expand_vars_to_list 2 finalized list[%d]=%p '%s' "
+                                                       "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+                                                       strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+                                               list[n++] = pos;
+                                       }
+                               }
+                       } 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) {
+                                       strcpy(pos, global_argv[i]);
+                                       pos += strlen(global_argv[i]);
+                                       if (++i >= global_argc)
+                                               break;
+                                       *pos++ = '\0';
+                                       if (n) debug_printf_expand("expand_vars_to_list 3 finalized list[%d]=%p '%s' "
+                                               "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+                                                       strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+                                       list[n++] = pos;
+                               }
+                       } else { /* quoted $*: add as one word */
+                               while (1) {
+                                       strcpy(pos, global_argv[i]);
+                                       pos += strlen(global_argv[i]);
+                                       if (!global_argv[++i])
+                                               break;
+                                       if (ifs[0])
+                                               *pos++ = ifs[0];
+                               }
+                       }
+                       break;
+               default:
+                       *p = '\0';
+                       arg[0] = first_ch & 0x7f;
+                       if (isdigit(arg[0])) {
+                               i = xatoi_u(arg);
+                               val = NULL;
+                               if (i < global_argc)
+                                       val = global_argv[i];
+                       } else
+                               val = lookup_param(arg);
+                       arg[0] = first_ch;
+                       *p = SPECIAL_VAR_SYMBOL;
+                       if (!(first_ch & 0x80)) { /* unquoted $VAR */
+                               if (val) {
+                                       n = expand_on_ifs(list, n, &pos, val);
+                                       val = NULL;
+                               }
+                       } /* else: quoted $VAR, val will be appended at pos */
+               }
+               if (val) {
+                       strcpy(pos, val);
+                       pos += strlen(val);
+               }
+               arg = ++p;
+       }
+       debug_printf_expand("expand_vars_to_list adding tail '%s' at %p\n", arg, pos);
+       strcpy(pos, arg);
+       pos += strlen(arg) + 1;
+       if (pos == list[n-1] + 1) { /* expansion is empty */
+               if (!(ored_ch & 0x80)) { /* all vars were not quoted... */
+                       debug_printf_expand("expand_vars_to_list list[%d] empty, going back\n", n);
+                       pos--;
+                       n--;
+               }
+       }
+
+       *posp = pos;
+       return n;
+}
+
+static char **expand_variables(char **argv, char or_mask)
+{
+       int n;
+       int count = 1;
+       int len = 0;
+       char *pos, **v, **list;
+
+       v = argv;
+       if (!*v) debug_printf_expand("count_var_expansion_space: "
+                       "argv[0]=NULL count=%d len=%d alloc_space=%d\n",
+                       count, len, sizeof(char*) * count + len);
+       while (*v) {
+               count_var_expansion_space(&count, &len, *v);
+               debug_printf_expand("count_var_expansion_space: "
+                       "'%s' count=%d len=%d alloc_space=%d\n",
+                       *v, count, len, sizeof(char*) * count + len);
+               v++;
+       }
+       len += sizeof(char*) * count; /* total to alloc */
+       list = xmalloc(len);
+       pos = (char*)(list + count);
+       debug_printf_expand("list=%p, list[0] should be %p\n", list, pos);
+       n = 0;
+       v = argv;
+       while (*v)
+               n = expand_vars_to_list(list, n, &pos, *v++, or_mask);
+
+       if (n) debug_printf_expand("finalized list[%d]=%p '%s' "
+               "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+               strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+       list[n] = NULL;
+
+#ifdef DEBUG_EXPAND
+       {
+               int m = 0;
+               while (m <= n) {
+                       debug_printf_expand("list[%d]=%p '%s'\n", m, list[m], list[m]);
+                       m++;
+               }
+               debug_printf_expand("used_space=%d\n", pos - (char*)list);
+       }
+#endif
+       if (ENABLE_HUSH_DEBUG)
+               if (pos - (char*)list > len)
+                       bb_error_msg_and_die("BUG in varexp");
+       return list;
+}
+
+static char **expand_strvec_to_strvec(char **argv)
+{
+       return expand_variables(argv, 0);
+}
+
+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 (ENABLE_HUSH_DEBUG)
+               if (!list[0] || list[1])
+                       bb_error_msg_and_die("BUG in varexp2");
+       /* actually, just move string 2*sizeof(char*) bytes back */
+       strcpy((char*)list, list[0]);
+       debug_printf_expand("string_to_string='%s'\n", (char*)list);
+       return (char*)list;
+}
+
+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 (ENABLE_HUSH_DEBUG)
+                               if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
+                                       bb_error_msg_and_die("BUG in varexp3");
+                       list[n][-1] = ' '; /* TODO: or to ifs[0]? */
+                       n++;
+               }
+       }
+       strcpy((char*)list, list[0]);
+       debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
+       return (char*)list;
+}
+
+/* This is used to get/check local shell variables */
+static struct variable *get_local_var(const char *name)
+{
+       struct variable *cur;
+       int len;
+
+       if (!name)
+               return NULL;
+       len = strlen(name);
+       for (cur = top_var; cur; cur = cur->next) {
+               if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
+                       return cur;
+       }
+       return NULL;
+}
+
+/* str holds "NAME=VAL" and is expected to be malloced.
+ * We take ownership of it. */
+static int set_local_var(char *str, int flg_export)
+{
+       struct variable *cur;
+       char *value;
+       int name_len;
+
+       value = strchr(str, '=');
+       if (!value) { /* not expected to ever happen? */
+               free(str);
+               return -1;
+       }
+
+       name_len = value - str + 1; /* including '=' */
+       cur = 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 */
+               *value = '\0';
+               if (cur->flg_read_only) {
+                       bb_error_msg("%s: readonly variable", str);
+                       free(str);
+                       return -1;
+               }
+               unsetenv(str); /* just in case */
+               *value = '=';
+               if (strcmp(cur->varstr, str) == 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;
+ exp:
+       if (flg_export)
+               cur->flg_export = 1;
+       if (cur->flg_export)
+               return putenv(cur->varstr);
+       return 0;
+}
+
+static void unset_local_var(const char *name)
+{
+       struct variable *cur;
+       struct variable *prev = prev; /* for gcc */
+       int name_len;
+
+       if (!name)
+               return;
+       name_len = strlen(name);
+       cur = top_var;
+       while (cur) {
+               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;
+                       }
+               /* prev is ok to use here because 1st variable, HUSH_VERSION,
+                * is ro, and we cannot reach this code on the 1st pass */
+                       prev->next = cur->next;
+                       unsetenv(cur->varstr);
+                       if (!cur->max_len)
+                               free(cur->varstr);
+                       free(cur);
+                       return;
+               }
+               prev = cur;
+               cur = cur->next;
+       }
+}
+
+static int is_assignment(const char *s)
+{
+       if (!s || !isalpha(*s))
+               return 0;
+       s++;
+       while (isalnum(*s) || *s == '_')
+               s++;
+       return *s == '=';
+}
+
+/* the src parameter allows us to peek forward to a possible &n syntax
+ * for file descriptor duplication, e.g., "2>&1".
+ * Return code is 0 normally, 1 if a syntax error is detected in src.
+ * Resource errors (in xmalloc) cause the process to exit */
+static int setup_redirect(struct p_context *ctx, int fd, redir_type style,
+       struct in_str *input)
+{
+       struct child_prog *child = ctx->child;
+       struct redir_struct *redir = child->redirects;
+       struct redir_struct *last_redir = NULL;
+
+       /* Create a new redir_struct and drop it onto the end of the linked list */
+       while (redir) {
+               last_redir = redir;
+               redir = redir->next;
+       }
+       redir = xzalloc(sizeof(struct redir_struct));
+       /* redir->next = NULL; */
+       /* redir->glob_word = NULL; */
+       if (last_redir) {
+               last_redir->next = redir;
+       } else {
+               child->redirects = redir;
+       }
+
+       redir->type = style;
+       redir->fd = (fd == -1) ? redir_table[style].default_fd : fd;
+
+       debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip);
+
+       /* Check for a '2>&1' type redirect */
+       redir->dup = redirect_dup_num(input);
+       if (redir->dup == -2) return 1;  /* syntax error */
+       if (redir->dup != -1) {
+               /* Erik had a check here that the file descriptor in question
+                * is legit; I postpone that to "run time"
+                * A "-" representation of "close me" shows up as a -3 here */
+               debug_printf("Duplicating redirect '%d>&%d'\n", redir->fd, redir->dup);
+       } else {
+               /* We do _not_ try to open the file that src points to,
+                * since we need to return and let src be expanded first.
+                * Set ctx->pending_redirect, so we know what to do at the
+                * end of the next parsed word. */
+               ctx->pending_redirect = redir;
+       }
+       return 0;
+}
+
+static struct pipe *new_pipe(void)
+{
+       struct pipe *pi;
+       pi = xzalloc(sizeof(struct pipe));
+       /*pi->num_progs = 0;*/
+       /*pi->progs = NULL;*/
+       /*pi->next = NULL;*/
+       /*pi->followup = 0;  invalid */
+       if (RES_NONE)
+               pi->res_word = RES_NONE;
+       return pi;
+}
+
+static void initialize_context(struct p_context *ctx)
+{
+       ctx->child = NULL;
+       ctx->pipe = ctx->list_head = new_pipe();
+       ctx->pending_redirect = NULL;
+       ctx->res_w = RES_NONE;
+       //only ctx->parse_type is not touched... is this intentional?
+       ctx->old_flag = 0;
+       ctx->stack = NULL;
+       done_command(ctx);   /* creates the memory for working child */
+}
+
+/* normal return is 0
+ * if a reserved word is found, and processed, return 1
+ * should handle if, then, elif, else, fi, for, while, until, do, done.
+ * case, function, and select are obnoxious, save those for later.
+ */
+#if ENABLE_HUSH_IF || ENABLE_HUSH_LOOPS
+static int reserved_word(o_string *dest, struct p_context *ctx)
+{
+       struct reserved_combo {
+               char literal[7];
+               unsigned char code;
+               int flag;
+       };
+       /* Mostly a list of accepted follow-up reserved words.
+        * FLAG_END means we are done with the sequence, and are ready
+        * to turn the compound list into a command.
+        * FLAG_START means the word must start a new compound list.
+        */
+       static const struct reserved_combo reserved_list[] = {
+#if ENABLE_HUSH_IF
+               { "if",    RES_IF,    FLAG_THEN | FLAG_START },
+               { "then",  RES_THEN,  FLAG_ELIF | FLAG_ELSE | FLAG_FI },
+               { "elif",  RES_ELIF,  FLAG_THEN },
+               { "else",  RES_ELSE,  FLAG_FI   },
+               { "fi",    RES_FI,    FLAG_END  },
+#endif
+#if ENABLE_HUSH_LOOPS
+               { "for",   RES_FOR,   FLAG_IN   | FLAG_START },
+               { "while", RES_WHILE, FLAG_DO   | FLAG_START },
+               { "until", RES_UNTIL, FLAG_DO   | FLAG_START },
+               { "in",    RES_IN,    FLAG_DO   },
+               { "do",    RES_DO,    FLAG_DONE },
+               { "done",  RES_DONE,  FLAG_END  }
+#endif
+       };
+
+       const struct reserved_combo *r;
+
+       for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
+               if (strcmp(dest->data, r->literal) != 0)
+                       continue;
+               debug_printf("found reserved word %s, code %d\n", r->literal, r->code);
+               if (r->flag & FLAG_START) {
+                       struct p_context *new;
+                       debug_printf("push stack\n");
+#if ENABLE_HUSH_LOOPS
+                       if (ctx->res_w == RES_IN || ctx->res_w == RES_FOR) {
+                               syntax("malformed for"); /* example: 'for if' */
+                               ctx->res_w = RES_SNTX;
+                               b_reset(dest);
+                               return 1;
+                       }
+#endif
+                       new = xmalloc(sizeof(*new));
+                       *new = *ctx;   /* physical copy */
+                       initialize_context(ctx);
+                       ctx->stack = new;
+               } else if (ctx->res_w == RES_NONE || !(ctx->old_flag & (1 << r->code))) {
+                       syntax(NULL);
+                       ctx->res_w = RES_SNTX;
+                       b_reset(dest);
+                       return 1;
+               }
+               ctx->res_w = r->code;
+               ctx->old_flag = r->flag;
+               if (ctx->old_flag & FLAG_END) {
+                       struct p_context *old;
+                       debug_printf("pop stack\n");
+                       done_pipe(ctx, PIPE_SEQ);
+                       old = ctx->stack;
+                       old->child->group = ctx->list_head;
+                       old->child->subshell = 0;
+                       *ctx = *old;   /* physical copy */
+                       free(old);
+               }
+               b_reset(dest);
+               return 1;
+       }
+       return 0;
+}
+#else
+#define reserved_word(dest, ctx) ((int)0)
+#endif
+
+/* Normal return is 0.
+ * Syntax or xglob errors return 1. */
+static int done_word(o_string *dest, struct p_context *ctx)
+{
+       struct child_prog *child = ctx->child;
+       char ***glob_target;
+       int gr;
+
+       debug_printf_parse("done_word entered: '%s' %p\n", dest->data, child);
+       if (dest->length == 0 && !dest->nonnull) {
+               debug_printf_parse("done_word return 0: true null, ignored\n");
+               return 0;
+       }
+       if (ctx->pending_redirect) {
+               glob_target = &ctx->pending_redirect->glob_word;
+       } else {
+               if (child->group) {
+                       syntax(NULL);
+                       debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n");
+                       return 1;
+               }
+               if (!child->argv && (ctx->parse_type & PARSEFLAG_SEMICOLON)) {
+                       debug_printf_parse(": checking '%s' for reserved-ness\n", dest->data);
+                       if (reserved_word(dest, ctx)) {
+                               debug_printf_parse("done_word return %d\n", (ctx->res_w == RES_SNTX));
+                               return (ctx->res_w == RES_SNTX);
+                       }
+               }
+               glob_target = &child->argv;
+       }
+       gr = xglob(dest, glob_target);
+       if (gr != 0) {
+               debug_printf_parse("done_word return 1: xglob returned %d\n", gr);
+               return 1;
+       }
+
+       b_reset(dest);
+       if (ctx->pending_redirect) {
+               /* NB: don't free_strings(ctx->pending_redirect->glob_word) here */
+               if (ctx->pending_redirect->glob_word
+                && ctx->pending_redirect->glob_word[0]
+                && ctx->pending_redirect->glob_word[1]
+               ) {
+                       /* more than one word resulted from globbing redir */
+                       ctx->pending_redirect = NULL;
+                       bb_error_msg("ambiguous redirect");
+                       debug_printf_parse("done_word return 1: ambiguous redirect\n");
+                       return 1;
+               }
+               ctx->pending_redirect = NULL;
+       }
+#if ENABLE_HUSH_LOOPS
+       if (ctx->res_w == RES_FOR) {
+               done_word(dest, ctx);
+               done_pipe(ctx, PIPE_SEQ);
+       }
+#endif
+       debug_printf_parse("done_word return 0\n");
+       return 0;
+}
+
+/* The only possible error here is out of memory, in which case
+ * xmalloc exits. */
+static int done_command(struct p_context *ctx)
+{
+       /* The child is really already in the pipe structure, so
+        * advance the pipe counter and make a new, null child. */
+       struct pipe *pi = ctx->pipe;
+       struct child_prog *child = ctx->child;
+
+       if (child) {
+               if (child->group == NULL
+                && child->argv == NULL
+                && child->redirects == NULL
+               ) {
+                       debug_printf_parse("done_command: skipping null cmd, num_progs=%d\n", pi->num_progs);
+                       return pi->num_progs;
+               }
+               pi->num_progs++;
+               debug_printf_parse("done_command: ++num_progs=%d\n", pi->num_progs);
+       } else {
+               debug_printf_parse("done_command: initializing, num_progs=%d\n", pi->num_progs);
+       }
+
+       /* Only real trickiness here is that the uncommitted
+        * child structure is not counted in pi->num_progs. */
+       pi->progs = xrealloc(pi->progs, sizeof(*pi->progs) * (pi->num_progs+1));
+       child = &pi->progs[pi->num_progs];
+
+       memset(child, 0, sizeof(*child));
+       /*child->redirects = NULL;*/
+       /*child->argv = NULL;*/
+       /*child->is_stopped = 0;*/
+       /*child->group = NULL;*/
+       child->family = pi;
+       //sp: /*child->sp = 0;*/
+       //pt: child->parse_type = ctx->parse_type;
+
+       ctx->child = child;
+       /* but ctx->pipe and ctx->list_head remain unchanged */
+
+       return pi->num_progs; /* used only for 0/nonzero check */
+}
+
+static int done_pipe(struct p_context *ctx, pipe_style type)
+{
+       struct pipe *new_p;
+       int not_null;
+
+       debug_printf_parse("done_pipe entered, followup %d\n", type);
+       not_null = done_command(ctx);  /* implicit closure of previous command */
+       ctx->pipe->followup = type;
+       ctx->pipe->res_word = ctx->res_w;
+       /* 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) {
+               new_p = new_pipe();
+               ctx->pipe->next = new_p;
+               ctx->pipe = new_p;
+               ctx->child = NULL;
+               done_command(ctx);  /* set up new pipe to accept commands */
+       }
+       debug_printf_parse("done_pipe return 0\n");
+       return 0;
+}
+
+/* peek ahead in the in_str to find out if we have a "&n" construct,
+ * as in "2>&1", that represents duplicating a file descriptor.
+ * returns either -2 (syntax error), -1 (no &), or the number found.
+ */
+static int redirect_dup_num(struct in_str *input)
+{
+       int ch, d = 0, ok = 0;
+       ch = b_peek(input);
+       if (ch != '&') return -1;
+
+       b_getch(input);  /* get the & */
+       ch = b_peek(input);
+       if (ch == '-') {
+               b_getch(input);
+               return -3;  /* "-" represents "close me" */
+       }
+       while (isdigit(ch)) {
+               d = d*10 + (ch-'0');
+               ok = 1;
+               b_getch(input);
+               ch = b_peek(input);
+       }
+       if (ok) return d;
+
+       bb_error_msg("ambiguous redirect");
+       return -2;
+}
+
+/* If a redirect is immediately preceded by a number, that number is
+ * supposed to tell which file descriptor to redirect.  This routine
+ * looks for such preceding numbers.  In an ideal world this routine
+ * needs to handle all the following classes of redirects...
+ *     echo 2>foo     # redirects fd  2 to file "foo", nothing passed to echo
+ *     echo 49>foo    # redirects fd 49 to file "foo", nothing passed to echo
+ *     echo -2>foo    # redirects fd  1 to file "foo",    "-2" passed to echo
+ *     echo 49x>foo   # redirects fd  1 to file "foo",   "49x" passed to echo
+ * A -1 output from this program means no valid number was found, so the
+ * caller should use the appropriate default for this redirection.
+ */
+static int redirect_opt_num(o_string *o)
+{
+       int num;
+
+       if (o->length == 0)
+               return -1;
+       for (num = 0; num < o->length; num++) {
+               if (!isdigit(*(o->data + num))) {
+                       return -1;
+               }
+       }
+       /* reuse num (and save an int) */
+       num = atoi(o->data);
+       b_reset(o);
+       return num;
+}
+
+#if ENABLE_HUSH_TICK
+/* NB: currently disabled on NOMMU */
+static FILE *generate_stream_from_list(struct pipe *head)
+{
+       FILE *pf;
+       int pid, channel[2];
+
+       xpipe(channel);
+/* *** NOMMU WARNING *** */
+/* By using vfork here, we suspend parent till child exits or execs.
+ * If child will not do it before it fills the pipe, it can block forever
+ * in write(STDOUT_FILENO), and parent (shell) will be also stuck.
+ */
+       pid = BB_MMU ? fork() : vfork();
+       if (pid < 0)
+               bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
+       if (pid == 0) { /* child */
+               if (ENABLE_HUSH_JOB)
+                       die_sleep = 0; /* let nofork's xfuncs die */
+               close(channel[0]); /* NB: close _first_, then move fd! */
+               xmove_fd(channel[1], 1);
+               /* Prevent it from trying to handle ctrl-z etc */
+#if ENABLE_HUSH_JOB
+               run_list_level = 1;
+#endif
+               /* Process substitution is not considered to be usual
+                * 'command execution'.
+                * SUSv3 says ctrl-Z should be ignored, ctrl-C should not. */
+               /* Not needed, we are relying on it being disabled
+                * everywhere outside actual command execution. */
+               /*set_jobctrl_sighandler(SIG_IGN);*/
+               set_misc_sighandler(SIG_DFL);
+               /* Freeing 'head' here would break NOMMU. */
+               _exit(run_list(head));
+       }
+       close(channel[1]);
+       pf = fdopen(channel[0], "r");
+       return pf;
+       /* 'head' is freed by the caller */
+}
+
+/* Return code is exit status of the process that is run. */
+static int process_command_subs(o_string *dest,
+               /*struct p_context *ctx,*/
+               struct in_str *input,
+               const char *subst_end)
+{
+       int retcode, ch, eol_cnt;
+       o_string result = NULL_O_STRING;
+       struct p_context inner;
+       FILE *p;
+       struct in_str pipe_str;
+
+       initialize_context(&inner);
+
+       /* recursion to generate command */
+       retcode = parse_stream(&result, &inner, input, subst_end);
+       if (retcode != 0)
+               return retcode;  /* syntax error or EOF */
+       done_word(&result, &inner);
+       done_pipe(&inner, PIPE_SEQ);
+       b_free(&result);
+
+       p = generate_stream_from_list(inner.list_head);
+       if (p == NULL)
+               return 1;
+       close_on_exec_on(fileno(p));
+       setup_file_in_str(&pipe_str, p);
+
+       /* now send results of command back into original context */
+       eol_cnt = 0;
+       while ((ch = b_getch(&pipe_str)) != EOF) {
+               if (ch == '\n') {
+                       eol_cnt++;
+                       continue;
+               }
+               while (eol_cnt) {
+                       b_addqchr(dest, '\n', dest->o_quote);
+                       eol_cnt--;
+               }
+               b_addqchr(dest, ch, dest->o_quote);
+       }
+
+       debug_printf("done reading from pipe, pclose()ing\n");
+       /* This is the step that wait()s for the child.  Should be pretty
+        * safe, since we just read an EOF from its stdout.  We could try
+        * to do better, by using wait(), and keeping track of background jobs
+        * at the same time.  That would be a lot of work, and contrary
+        * to the KISS philosophy of this program. */
+       retcode = fclose(p);
+       free_pipe_list(inner.list_head, /* indent: */ 0);
+       debug_printf("closed FILE from child, retcode=%d\n", retcode);
+       return retcode;
+}
+#endif
+
+static int parse_group(o_string *dest, struct p_context *ctx,
+       struct in_str *input, int ch)
+{
+       int rcode;
+       const char *endch = NULL;
+       struct p_context sub;
+       struct child_prog *child = ctx->child;
+
+       debug_printf_parse("parse_group entered\n");
+       if (child->argv) {
+               syntax(NULL);
+               debug_printf_parse("parse_group return 1: syntax error, groups and arglists don't mix\n");
+               return 1;
+       }
+       initialize_context(&sub);
+       endch = "}";
+       if (ch == '(') {
+               endch = ")";
+               child->subshell = 1;
+       }
+       rcode = parse_stream(dest, &sub, input, endch);
+//vda: err chk?
+       done_word(dest, &sub); /* finish off the final word in the subcontext */
+       done_pipe(&sub, PIPE_SEQ);  /* and the final command there, too */
+       child->group = sub.list_head;
+
+       debug_printf_parse("parse_group return %d\n", rcode);
+       return rcode;
+       /* child remains "open", available for possible redirects */
+}
+
+/* Basically useful version until someone wants to get fancier,
+ * see the bash man page under "Parameter Expansion" */
+static const char *lookup_param(const char *src)
+{
+       struct variable *var = get_local_var(src);
+       if (var)
+               return strchr(var->varstr, '=') + 1;
+       return NULL;
+}
+
+/* return code: 0 for OK, 1 for syntax error */
+static int handle_dollar(o_string *dest, /*struct p_context *ctx,*/ struct in_str *input)
+{
+       int ch = b_peek(input);  /* first character after the $ */
+       unsigned char quote_mask = dest->o_quote ? 0x80 : 0;
+
+       debug_printf_parse("handle_dollar entered: ch='%c'\n", ch);
+       if (isalpha(ch)) {
+               b_addchr(dest, SPECIAL_VAR_SYMBOL);
+               //sp: ctx->child->sp++;
+               while (1) {
+                       debug_printf_parse(": '%c'\n", ch);
+                       b_getch(input);
+                       b_addchr(dest, ch | quote_mask);
+                       quote_mask = 0;
+                       ch = b_peek(input);
+                       if (!isalnum(ch) && ch != '_')
+                               break;
+               }
+               b_addchr(dest, SPECIAL_VAR_SYMBOL);
+       } else if (isdigit(ch)) {
+ make_one_char_var:
+               b_addchr(dest, SPECIAL_VAR_SYMBOL);
+               //sp: ctx->child->sp++;
+               debug_printf_parse(": '%c'\n", ch);
+               b_getch(input);
+               b_addchr(dest, ch | quote_mask);
+               b_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 '{':
+                       b_addchr(dest, SPECIAL_VAR_SYMBOL);
+                       //sp: ctx->child->sp++;
+                       b_getch(input);
+                       /* XXX maybe someone will try to escape the '}' */
+                       while (1) {
+                               ch = b_getch(input);
+                               if (ch == '}')
+                                       break;
+                               if (!isalnum(ch) && ch != '_') {
+                                       syntax("unterminated ${name}");
+                                       debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
+                                       return 1;
+                               }
+                               debug_printf_parse(": '%c'\n", ch);
+                               b_addchr(dest, ch | quote_mask);
+                               quote_mask = 0;
+                       }
+                       b_addchr(dest, SPECIAL_VAR_SYMBOL);
+                       break;
+#if ENABLE_HUSH_TICK
+               case '(':
+                       b_getch(input);
+                       process_command_subs(dest, /*ctx,*/ input, ")");
+                       break;
+#endif
+               case '-':
+               case '_':
+                       /* still unhandled, but should be eventually */
+                       bb_error_msg("unhandled syntax: $%c", ch);
+                       return 1;
+                       break;
+               default:
+                       b_addqchr(dest, '$', dest->o_quote);
+       }
+       debug_printf_parse("handle_dollar return 0\n");
+       return 0;
+}
+
+/* return code is 0 for normal exit, 1 for syntax error */
+static int parse_stream(o_string *dest, struct p_context *ctx,
+       struct in_str *input, const char *end_trigger)
+{
+       int ch, m;
+       int redir_fd;
+       redir_type redir_style;
+       int next;
+
+       /* Only double-quote state is handled in the state variable dest->o_quote.
+        * A single-quote triggers a bypass of the main loop until its mate is
+        * found.  When recursing, quote state is passed in via dest->o_quote. */
+
+       debug_printf_parse("parse_stream entered, end_trigger='%s'\n", end_trigger);
+
+       while (1) {
+               m = CHAR_IFS;
+               next = '\0';
+               ch = b_getch(input);
+               if (ch != EOF) {
+                       m = charmap[ch];
+                       if (ch != '\n')
+                               next = b_peek(input);
+               }
+               debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n",
+                                               ch, ch, m, dest->o_quote);
+               if (m == CHAR_ORDINARY
+                || (m != CHAR_SPECIAL && dest->o_quote)
+               ) {
+                       if (ch == EOF) {
+                               syntax("unterminated \"");
+                               debug_printf_parse("parse_stream return 1: unterminated \"\n");
+                               return 1;
+                       }
+                       b_addqchr(dest, ch, dest->o_quote);
+                       continue;
+               }
+               if (m == CHAR_IFS) {
+                       if (done_word(dest, ctx)) {
+                               debug_printf_parse("parse_stream return 1: done_word!=0\n");
+                               return 1;
+                       }
+                       if (ch == EOF)
+                               break;
+                       /* If we aren't performing a substitution, treat
+                        * a newline as a command separator.
+                        * [why we don't handle it exactly like ';'? --vda] */
+                       if (end_trigger && ch == '\n') {
+                               done_pipe(ctx, PIPE_SEQ);
+                       }
+               }
+               if ((end_trigger && strchr(end_trigger, ch))
+                && !dest->o_quote && ctx->res_w == RES_NONE
+               ) {
+                       debug_printf_parse("parse_stream return 0: end_trigger char found\n");
+                       return 0;
+               }
+               if (m == CHAR_IFS)
+                       continue;
+               switch (ch) {
+               case '#':
+                       if (dest->length == 0 && !dest->o_quote) {
+                               while (1) {
+                                       ch = b_peek(input);
+                                       if (ch == EOF || ch == '\n')
+                                               break;
+                                       b_getch(input);
+                               }
+                       } else {
+                               b_addqchr(dest, ch, dest->o_quote);
+                       }
+                       break;
+               case '\\':
+                       if (next == EOF) {
+                               syntax("\\<eof>");
+                               debug_printf_parse("parse_stream return 1: \\<eof>\n");
+                               return 1;
+                       }
+                       b_addqchr(dest, '\\', dest->o_quote);
+                       b_addqchr(dest, b_getch(input), dest->o_quote);
+                       break;
+               case '$':
+                       if (handle_dollar(dest, /*ctx,*/ input) != 0) {
+                               debug_printf_parse("parse_stream return 1: handle_dollar returned non-0\n");
+                               return 1;
+                       }
+                       break;
+               case '\'':
+                       dest->nonnull = 1;
+                       while (1) {
+                               ch = b_getch(input);
+                               if (ch == EOF || ch == '\'')
+                                       break;
+                               b_addchr(dest, ch);
+                       }
+                       if (ch == EOF) {
+                               syntax("unterminated '");
+                               debug_printf_parse("parse_stream return 1: unterminated '\n");
+                               return 1;
+                       }
+                       break;
+               case '"':
+                       dest->nonnull = 1;
+                       dest->o_quote ^= 1; /* invert */
+                       break;
+#if ENABLE_HUSH_TICK
+               case '`':
+                       process_command_subs(dest, /*ctx,*/ input, "`");
+                       break;
+#endif
+               case '>':
+                       redir_fd = redirect_opt_num(dest);
+                       done_word(dest, ctx);
+                       redir_style = REDIRECT_OVERWRITE;
+                       if (next == '>') {
+                               redir_style = REDIRECT_APPEND;
+                               b_getch(input);
+                       }
+#if 0
+                       else if (next == '(') {
+                               syntax(">(process) not supported");
+                               debug_printf_parse("parse_stream return 1: >(process) not supported\n");
+                               return 1;
+                       }
+#endif
+                       setup_redirect(ctx, redir_fd, redir_style, input);
+                       break;
+               case '<':
+                       redir_fd = redirect_opt_num(dest);
+                       done_word(dest, ctx);
+                       redir_style = REDIRECT_INPUT;
+                       if (next == '<') {
+                               redir_style = REDIRECT_HEREIS;
+                               b_getch(input);
+                       } else if (next == '>') {
+                               redir_style = REDIRECT_IO;
+                               b_getch(input);
+                       }
+#if 0
+                       else if (next == '(') {
+                               syntax("<(process) not supported");
+                               debug_printf_parse("parse_stream return 1: <(process) not supported\n");
+                               return 1;
+                       }
+#endif
+                       setup_redirect(ctx, redir_fd, redir_style, input);
+                       break;
+               case ';':
+                       done_word(dest, ctx);
+                       done_pipe(ctx, PIPE_SEQ);
+                       break;
+               case '&':
+                       done_word(dest, ctx);
+                       if (next == '&') {
+                               b_getch(input);
+                               done_pipe(ctx, PIPE_AND);
+                       } else {
+                               done_pipe(ctx, PIPE_BG);
+                       }
+                       break;
+               case '|':
+                       done_word(dest, ctx);
+                       if (next == '|') {
+                               b_getch(input);
+                               done_pipe(ctx, PIPE_OR);
+                       } else {
+                               /* we could pick up a file descriptor choice here
+                                * with redirect_opt_num(), but bash doesn't do it.
+                                * "echo foo 2| cat" yields "foo 2". */
+                               done_command(ctx);
+                       }
+                       break;
+               case '(':
+               case '{':
+                       if (parse_group(dest, ctx, input, ch) != 0) {
+                               debug_printf_parse("parse_stream return 1: parse_group returned non-0\n");
+                               return 1;
+                       }
+                       break;
+               case ')':
+               case '}':
+                       syntax("unexpected }");   /* Proper use of this character is caught by end_trigger */
+                       debug_printf_parse("parse_stream return 1: unexpected '}'\n");
+                       return 1;
+               default:
+                       if (ENABLE_HUSH_DEBUG)
+                               bb_error_msg_and_die("BUG: unexpected %c\n", ch);
+               }
+       }
+       /* Complain if quote?  No, maybe we just finished a command substitution
+        * that was quoted.  Example:
+        * $ echo "`cat foo` plus more"
+        * and we just got the EOF generated by the subshell that ran "cat foo"
+        * The only real complaint is if we got an EOF when end_trigger != NULL,
+        * that is, we were really supposed to get end_trigger, and never got
+        * one before the EOF.  Can't use the standard "syntax error" return code,
+        * so that parse_stream_outer can distinguish the EOF and exit smoothly. */
+       debug_printf_parse("parse_stream return %d\n", -(end_trigger != NULL));
+       if (end_trigger)
+               return -1;
+       return 0;
+}
+
+static void set_in_charmap(const char *set, int code)
+{
+       while (*set)
+               charmap[(unsigned char)*set++] = code;
+}
+
+static void update_charmap(void)
+{
+       /* char *ifs and char charmap[256] are both globals. */
+       ifs = getenv("IFS");
+       if (ifs == NULL)
+               ifs = " \t\n";
+       /* Precompute a list of 'flow through' behavior so it can be treated
+        * quickly up front.  Computation is necessary because of IFS.
+        * Special case handling of IFS == " \t\n" is not implemented.
+        * The charmap[] array only really needs two bits each,
+        * and on most machines that would be faster (reduced L1 cache use).
+        */
+       memset(charmap, CHAR_ORDINARY, sizeof(charmap));
+#if ENABLE_HUSH_TICK
+       set_in_charmap("\\$\"`", CHAR_SPECIAL);
+#else
+       set_in_charmap("\\$\"", CHAR_SPECIAL);
+#endif
+       set_in_charmap("<>;&|(){}#'", CHAR_ORDINARY_IF_QUOTED);
+       set_in_charmap(ifs, CHAR_IFS);  /* are ordinary if quoted */
+}
+
+/* most recursion does not come through here, the exception is
+ * from builtin_source() and builtin_eval() */
+static int parse_and_run_stream(struct in_str *inp, int parse_flag)
+{
+       struct p_context ctx;
+       o_string temp = NULL_O_STRING;
+       int rcode;
+       do {
+               ctx.parse_type = parse_flag;
+               initialize_context(&ctx);
+               update_charmap();
+               if (!(parse_flag & PARSEFLAG_SEMICOLON) || (parse_flag & PARSEFLAG_REPARSING))
+                       set_in_charmap(";$&|", CHAR_ORDINARY);
+#if ENABLE_HUSH_INTERACTIVE
+               inp->promptmode = 0; /* PS1 */
+#endif
+               /* We will stop & execute after each ';' or '\n'.
+                * Example: "sleep 9999; echo TEST" + ctrl-C:
+                * TEST should be printed */
+               rcode = parse_stream(&temp, &ctx, inp, ";\n");
+               if (rcode != 1 && ctx.old_flag != 0) {
+                       syntax(NULL);
+               }
+               if (rcode != 1 && ctx.old_flag == 0) {
+                       done_word(&temp, &ctx);
+                       done_pipe(&ctx, PIPE_SEQ);
+                       debug_print_tree(ctx.list_head, 0);
+                       debug_printf_exec("parse_stream_outer: run_and_free_list\n");
+                       run_and_free_list(ctx.list_head);
+               } else {
+                       if (ctx.old_flag != 0) {
+                               free(ctx.stack);
+                               b_reset(&temp);
+                       }
+                       temp.nonnull = 0;
+                       temp.o_quote = 0;
+                       inp->p = NULL;
+                       free_pipe_list(ctx.list_head, /* indent: */ 0);
+               }
+               b_free(&temp);
+       } while (rcode != -1 && !(parse_flag & PARSEFLAG_EXIT_FROM_LOOP));   /* loop on syntax errors, return on EOF */
+       return 0;
+}
+
+static int parse_and_run_string(const char *s, int parse_flag)
+{
+       struct in_str input;
+       setup_string_in_str(&input, s);
+       return parse_and_run_stream(&input, parse_flag);
+}
+
+static int parse_and_run_file(FILE *f)
+{
+       int rcode;
+       struct in_str input;
+       setup_file_in_str(&input, f);
+       rcode = parse_and_run_stream(&input, PARSEFLAG_SEMICOLON);
+       return rcode;
+}
+
+#if ENABLE_HUSH_JOB
+/* 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)
+{
+       pid_t shell_pgrp;
+
+       saved_task_pgrp = shell_pgrp = getpgrp();
+       debug_printf_jobs("saved_task_pgrp=%d\n", saved_task_pgrp);
+       close_on_exec_on(interactive_fd);
+
+       /* If we were ran as 'hush &',
+        * sleep until we are in the foreground.  */
+       while (tcgetpgrp(interactive_fd) != shell_pgrp) {
+               /* Send TTIN to ourself (should stop us) */
+               kill(- shell_pgrp, SIGTTIN);
+               shell_pgrp = getpgrp();
+       }
+
+       /* Ignore job-control and misc signals.  */
+       set_jobctrl_sighandler(SIG_IGN);
+       set_misc_sighandler(SIG_IGN);
+//huh? signal(SIGCHLD, SIG_IGN);
+
+       /* We _must_ restore tty pgrp on fatal signals */
+       set_fatal_sighandler(sigexit);
+
+       /* Put ourselves in our own process group.  */
+       setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
+       /* Grab control of the terminal.  */
+       tcsetpgrp(interactive_fd, getpid());
+}
+#endif
+
+int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hush_main(int argc, char **argv)
+{
+       static const char version_str[] ALIGN1 = "HUSH_VERSION="HUSH_VER_STR;
+       static const struct variable const_shell_ver = {
+               .next = NULL,
+               .varstr = (char*)version_str,
+               .max_len = 1, /* 0 can provoke free(name) */
+               .flg_export = 1,
+               .flg_read_only = 1,
+       };
+
+       int opt;
+       FILE *input;
+       char **e;
+       struct variable *cur_var;
+
+       INIT_G();
+
+       /* Deal with HUSH_VERSION */
+       shell_ver = const_shell_ver; /* copying struct here */
+       top_var = &shell_ver;
+       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 = 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++;
+       }
+       putenv((char *)version_str); /* reinstate HUSH_VERSION */
+
+#if ENABLE_FEATURE_EDITING
+       line_input_state = new_line_input_t(FOR_SHELL);
+#endif
+       /* XXX what should these be while sourcing /etc/profile? */
+       global_argc = argc;
+       global_argv = argv;
+       /* Initialize some more globals to non-zero values */
+       set_cwd();
+#if ENABLE_HUSH_INTERACTIVE
+#if ENABLE_FEATURE_EDITING
+       cmdedit_set_initial_prompt();
+#endif
+       PS2 = "> ";
+#endif
+
+       if (EXIT_SUCCESS) /* otherwise is already done */
+               last_return_code = EXIT_SUCCESS;
+
+       if (argv[0] && argv[0][0] == '-') {
+               debug_printf("sourcing /etc/profile\n");
+               input = fopen("/etc/profile", "r");
+               if (input != NULL) {
+                       close_on_exec_on(fileno(input));
+                       parse_and_run_file(input);
+                       fclose(input);
+               }
+       }
+       input = stdin;
+
+       while ((opt = getopt(argc, argv, "c:xif")) > 0) {
+               switch (opt) {
+               case 'c':
+                       global_argv = argv + optind;
+                       global_argc = argc - optind;
+                       opt = parse_and_run_string(optarg, PARSEFLAG_SEMICOLON);
+                       goto final_return;
+               case 'i':
+                       /* Well, we cannot just declare interactiveness,
+                        * we have to have some stuff (ctty, etc) */
+                       /* interactive_fd++; */
+                       break;
+               case 'f':
+                       fake_mode = 1;
+                       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
+               }
+       }
+#if ENABLE_HUSH_JOB
+       /* A shell is interactive if the '-i' flag was given, or if all of
+        * the following conditions are met:
+        *    no -c command
+        *    no arguments remaining or the -s flag given
+        *    standard input is a terminal
+        *    standard output is a terminal
+        *    Refer to Posix.2, the description of the 'sh' utility. */
+       if (argv[optind] == NULL && input == stdin
+        && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
+       ) {
+               saved_tty_pgrp = tcgetpgrp(STDIN_FILENO);
+               debug_printf("saved_tty_pgrp=%d\n", saved_tty_pgrp);
+               if (saved_tty_pgrp >= 0) {
+                       /* try to dup to high fd#, >= 255 */
+                       interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+                       if (interactive_fd < 0) {
+                               /* try to dup to any fd */
+                               interactive_fd = dup(STDIN_FILENO);
+                               if (interactive_fd < 0)
+                                       /* give up */
+                                       interactive_fd = 0;
+                       }
+                       // TODO: track & disallow any attempts of user
+                       // to (inadvertently) close/redirect it
+               }
+       }
+       debug_printf("interactive_fd=%d\n", interactive_fd);
+       if (interactive_fd) {
+               fcntl(interactive_fd, F_SETFD, FD_CLOEXEC);
+               /* Looks like they want an interactive shell */
+               setup_job_control();
+               /* -1 is special - makes xfuncs longjmp, not exit
+                * (we reset die_sleep = 0 whereever we [v]fork) */
+               die_sleep = -1;
+               if (setjmp(die_jmp)) {
+                       /* xfunc has failed! die die die */
+                       hush_exit(xfunc_error_retval);
+               }
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+               printf("\n\n%s hush - the humble shell v"HUSH_VER_STR"\n", bb_banner);
+               printf("Enter 'help' for a list of built-in commands.\n\n");
+#endif
+       }
+#elif ENABLE_HUSH_INTERACTIVE
+/* no job control compiled, only prompt/line editing */
+       if (argv[optind] == NULL && input == stdin
+        && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
+       ) {
+               interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+               if (interactive_fd < 0) {
+                       /* try to dup to any fd */
+                       interactive_fd = dup(STDIN_FILENO);
+                       if (interactive_fd < 0)
+                               /* give up */
+                               interactive_fd = 0;
+               }
+               if (interactive_fd)
+                       fcntl(interactive_fd, F_SETFD, FD_CLOEXEC);
+       }
+#endif
+
+       if (argv[optind] == NULL) {
+               opt = parse_and_run_file(stdin);
+       } else {
+               debug_printf("\nrunning script '%s'\n", argv[optind]);
+               global_argv = argv + optind;
+               global_argc = argc - optind;
+               input = xfopen(argv[optind], "r");
+               fcntl(fileno(input), F_SETFD, FD_CLOEXEC);
+               opt = parse_and_run_file(input);
+       }
+
+ final_return:
+
+#if ENABLE_FEATURE_CLEAN_UP
+       fclose(input);
+       if (cwd != bb_msg_unknown)
+               free((char*)cwd);
+       cur_var = 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(opt ? opt : last_return_code);
+}
+
+
+#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
diff --git a/shell/hush_doc.txt b/shell/hush_doc.txt
new file mode 100644 (file)
index 0000000..973fe44
--- /dev/null
@@ -0,0 +1,39 @@
+       This is how hush runs commands:
+
+/* 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 100644 (file)
index 0000000..54a19aa
--- /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" >temp1
+for freed in $freelist; do
+    echo Dropping $freed
+    cat temp1 | grep -v $freed >temp2
+    mv temp2 temp1
+done
diff --git a/shell/hush_test/hush-bugs/quote3.right b/shell/hush_test/hush-bugs/quote3.right
new file mode 100644 (file)
index 0000000..069a46e
--- /dev/null
@@ -0,0 +1,3 @@
+Testing: in $empty""
+..
+Finished
diff --git a/shell/hush_test/hush-bugs/quote3.tests b/shell/hush_test/hush-bugs/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/hush_test/hush-bugs/tick.right b/shell/hush_test/hush-bugs/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-bugs/tick.tests b/shell/hush_test/hush-bugs/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-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/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..08a270c
--- /dev/null
@@ -0,0 +1,2 @@
+shown
+hush: syntax error: unterminated '
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-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/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/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/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-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/var.right b/shell/hush_test/hush-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/hush_test/hush-vars/var.tests b/shell/hush_test/hush-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/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_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..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/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..7bccc1e
--- /dev/null
@@ -0,0 +1,2 @@
+Measuring memory leak...
+vsz does not grow
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..388d6a7
--- /dev/null
@@ -0,0 +1,91 @@
+pid=$$
+
+# Warm up
+beg=`ps -o pid,vsz | grep "^ *$pid "`
+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
+done
+end=`ps -o pid,vsz | grep "^ *$pid "`
+
+echo "Measuring memory leak..."
+beg=`ps -o pid,vsz | grep "^ *$pid "`
+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
+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/run-all b/shell/hush_test/run-all
new file mode 100755 (executable)
index 0000000..805f75a
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test -x hush || {
+    echo "No ./hush?! Perhaps you want to run 'ln -s ../../busybox hush'"
+    exit
+}
+
+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
+#   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 hush-*`
+
+    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/hush_test/zbad b/shell/hush_test/zbad
new file mode 100644 (file)
index 0000000..e4b5caa
--- /dev/null
@@ -0,0 +1,3 @@
+# TODO: hush doesn't know ':' null command
+
+while :; do exit; done
diff --git a/shell/hush_test/zbad2 b/shell/hush_test/zbad2
new file mode 100644 (file)
index 0000000..b6fffe5
--- /dev/null
@@ -0,0 +1,19 @@
+## TODO: fix and add to testsuite
+
+## # bash zbad2
+## ZVAR=z.map
+## *.map
+## # hush zbad2
+## ZVAR=z.map
+## z.map  <====== !!!
+
+## hush does globbing for "VAR=val" too!
+## it should do it only for non-assignments.
+## even if word looks like assignment, it can be non-assignment:
+## ZVAR=*.map /bin/echo ZVAR=*.map
+## ^dont_glob           ^glob
+
+>ZVAR=z.map
+ZVAR=*.map /bin/echo ZVAR=*.map
+ZVAR=*.map
+echo "$ZVAR"
diff --git a/shell/lash_unused.c b/shell/lash_unused.c
new file mode 100644 (file)
index 0000000..d57f584
--- /dev/null
@@ -0,0 +1,1570 @@
+/* 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) ATTRIBUTE_NORETURN;
+static int busy_loop(FILE * input);
+
+
+/* Table of built-in functions (these are non-forking builtins, meaning they
+ * can change global variables in the parent shell process but they will not
+ * work with pipes and redirects; 'unset foo | whatever' will not work) */
+static 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 ATTRIBUTE_UNUSED *format, ...) { }
+#endif
+
+/*
+       Most builtins need access to the struct child_prog that has
+       their arguments, previously coded as cmd->progs[0].  That coding
+       can exhibit a bug, if the builtin is not the first command in
+       a pipeline: "echo foo | exec sort" will attempt to exec foo.
+
+builtin   previous use      notes
+------ -----------------  ---------
+cd      cmd->progs[0]
+exec    cmd->progs[0]  squashed bug: didn't look for applets or forking builtins
+exit    cmd->progs[0]
+fg_bg   cmd->progs[0], job_list->head, job_list->fg
+help    0
+jobs    job_list->head
+pwd     0
+export  cmd->progs[0]
+source  cmd->progs[0]
+unset   cmd->progs[0]
+read    cmd->progs[0]
+
+I added "struct job *family;" to struct child_prog,
+and switched API to builtin_foo(struct child_prog *child);
+So   cmd->text        becomes  child->family->text
+     cmd->job_context  becomes  child->family->job_context
+     cmd->progs[0]    becomes  *child
+     job_list          becomes  child->family->job_list
+ */
+
+
+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 ATTRIBUTE_UNUSED *dummy)
+{
+       const struct built_in_command *x;
+
+       printf("\nBuilt-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 ATTRIBUTE_UNUSED *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("/etc/profile", "r");
+               if (prof_input) {
+                       llist_add_to(&close_me_list, (void *)(long)fileno(prof_input));
+                       /* Now run the file */
+                       busy_loop(prof_input);
+                       fclose_if_not_stdin(prof_input);
+                       llist_pop(&close_me_list);
+               }
+       }
+
+       opt = getopt32(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(global_argv[optind], "r");
+               /* be lazy, never mark this closed */
+               llist_add_to(&close_me_list, (void *)(long)fileno(input));
+       }
+
+       /* initialize the cwd -- this is never freed...*/
+       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/msh.c b/shell/msh.c
new file mode 100644 (file)
index 0000000..63f3659
--- /dev/null
@@ -0,0 +1,5322 @@
+/* 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 CONFIG_BUSYBOX_EXEC_PATH "/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 ATTRIBUTE_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 *scantree(struct op *);
+static struct op *dowholefile(int /*, int*/);
+
+
+/* Globals */
+static char **dolv;
+static int dolc;
+static int 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)
+               write(2, s, strlen(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) ATTRIBUTE_NORETURN;
+static void fail(void)
+{
+       longjmp(failpt, 1);
+       /* NOTREACHED */
+}
+
+/* abort shell (or fail in subshell) */
+static void leave(void) ATTRIBUTE_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);
+               exstat = -1;
+       }
+       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(": cannot open");
+                       return 1;
+               }
+       }
+
+       next(remap(f));
+       return 0;
+}
+
+
+struct op *scantree(struct op *head)
+{
+       struct op *dotnode;
+
+       if (head == NULL)
+               return NULL;
+
+       if (head->left != NULL) {
+               dotnode = scantree(head->left);
+               if (dotnode)
+                       return dotnode;
+       }
+
+       if (head->right != NULL) {
+               dotnode = scantree(head->right);
+               if (dotnode)
+                       return dotnode;
+       }
+
+       if (head->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;
+}
+
+
+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 ATTRIBUTE_UNUSED) /* 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) ATTRIBUTE_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) ATTRIBUTE_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;
+               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;
+               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, cannot vfork()!\n"));
+                       return -1;
+               }
+
+               if (newpid > 0) {  /* Parent */
+                       /* Restore values */
+                       pin = hpin;
+                       pout = hpout;
+                       *wp = hwp;
+                       interactive = hinteractive;
+                       intr = hintr;
+                       brklist = hbrklist;
+                       execflg = hexecflg;
+
+                       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(": cannot 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(": cannot 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(0);
+
+       cp = rexecve(wp[0], wp, makenv(0, NULL));
+       prs(wp[0]);
+       prs(": ");
+       err(cp);
+       if (!execflg)
+               trap[0] = NULL;
+
+       DBGPRINTF(("FORKEXEC: calling leave(), pid=%d\n", 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(": cannot ");
+               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 = -1;
+                       } else
+                               rv = WAITVAL(s);
+               }
+       } while (pid != lastpid);
+       heedint = oheedint;
+       if (intr) {
+               if (interactive) {
+                       if (canintr)
+                               intr = 0;
+               } else {
+                       if (exstat == 0)
+                               exstat = rv;
+                       onintr(0);
+               }
+       }
+       return rv;
+}
+
+static int setstatus(int s)
+{
+       exstat = s;
+       setval(lookup("?"), putn(s));
+       return s;
+}
+
+/*
+ * PATH-searching interface to execve.
+ * If getenv("PATH") were kept up-to-date,
+ * execvp might be used.
+ */
+static 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);
+               //for (i = 0; (*tp++ = c[i++]) != '\0';)
+               //      continue;
+
+               DBGPRINTF3(("REXECVE: global_env.linep is %s\n", global_env.linep));
+
+               execve(global_env.linep, v, envp);
+
+               switch (errno) {
+               case ENOEXEC:
+                       *v = global_env.linep;
+                       v--;
+                       tp = *v;
+                       *v = global_env.linep;
+                       execve(DEFAULT_SHELL, v, envp);
+                       *v = tp;
+                       return "no shell";
+
+               case ENOMEM:
+                       return (char *) bb_msg_memory_exhausted;
+
+               case E2BIG:
+                       return "argument list too long";
+               }
+       }
+       return errno == ENOENT ? "not found" : "cannot execute";
+}
+
+/*
+ * Run the command produced by generator `f'
+ * applied to stream `arg'.
+ */
+static int run(struct ioarg *argp, int (*f) (struct ioarg *))
+{
+       struct op *otree;
+       struct wdblock *swdlist;
+       struct wdblock *siolist;
+       jmp_buf ev, rt;
+       xint *ofail;
+       int rv;
+
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &rv;
+#endif
+
+       DBGPRINTF(("RUN: enter, areanum %d, outtree %p, failpt %p\n",
+                          areanum, outtree, failpt));
+
+       areanum++;
+       swdlist = wdlist;
+       siolist = iolist;
+       otree = outtree;
+       ofail = failpt;
+       rv = -1;
+
+       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 ATTRIBUTE_UNUSED, char **args ATTRIBUTE_UNUSED)
+{
+       int col;
+       const struct builtincmd *x;
+
+       puts("\nBuilt-in commands:\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 ATTRIBUTE_UNUSED, char **args ATTRIBUTE_UNUSED)
+{
+       return 0;
+}
+
+static int dochdir(struct op *t ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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(0, 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 ATTRIBUTE_UNUSED, char **args)
+{
+       return RUN(awordlist, args + 1, wdchar);
+}
+
+static int dotrap(struct op *t ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, char **args)
+{
+       return brkcontin(args[1], 1);
+}
+
+static int docontinue(struct op *t ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, char **args)
+{
+       rdexp(args + 1, export, EXPORT);
+       return 0;
+}
+
+static int doreadonly(struct op *t ATTRIBUTE_UNUSED, 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 ATTRIBUTE_UNUSED, 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, 1);
+               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 == '_') {
+               write(out, s, strlen(s));
+               write(out, "\n", 1);
+       }
+}
+
+
+/*
+ * Copyright (c) 1999 Herbert Xu <herbert@debian.org>
+ * This file contains code for the times builtin.
+ */
+static 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 ATTRIBUTE_UNUSED, char **args ATTRIBUTE_UNUSED)
+{
+       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("cannot 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(1);
+}
+
+
+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(2, &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(0);
+                       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 = (int) (((char) (mshdbg_var->value[0])) - '0');
+                       if (mshdbg < 0)
+                               mshdbg = 0;
+#endif
+                       DBGPRINTF(("MSH_MAIN: calling newfile()\n"));
+
+                       name = *++argv;
+                       if (newfile(name))
+                               exit(1);                /* 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_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/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..43bc9fc
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test -x msh || {
+    echo "No ./msh?! Perhaps you want to run 'ln -s ../../busybox msh'"
+    exit
+}
+
+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..4312a05
--- /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..0d5b2b9
--- /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)           += logger.o
+lib-$(CONFIG_LOGREAD)          += logread.o
+lib-$(CONFIG_SYSLOGD)          += syslogd.o
diff --git a/sysklogd/klogd.c b/sysklogd/klogd.c
new file mode 100644 (file)
index 0000000..983a597
--- /dev/null
@@ -0,0 +1,121 @@
+/* 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 ATTRIBUTE_UNUSED)
+{
+       klogctl(7, NULL, 0);
+       klogctl(0, NULL, 0);
+       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 ATTRIBUTE_UNUSED, char **argv)
+{
+       int i = i; /* silence gcc */
+       char *start;
+
+       /* do normal option parsing */
+       getopt32(argv, "c:n", &start);
+
+       if (option_mask32 & OPT_LEVEL) {
+               /* Valid levels are between 1 and 8 */
+               i = xatoul_range(start, 1, 8);
+       }
+
+       if (!(option_mask32 & OPT_FOREGROUND)) {
+               bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+       }
+
+       openlog("kernel", 0, LOG_KERN);
+
+       /* Set up sig handlers */
+       bb_signals(0
+               + (1 << SIGINT)
+               + (1 << SIGTERM)
+               , klogd_signal);
+       signal(SIGHUP, SIG_IGN);
+
+       /* "Open the log. Currently a NOP." */
+       klogctl(1, NULL, 0);
+
+       /* Set level of kernel console messaging. */
+       if (option_mask32 & OPT_LEVEL)
+               klogctl(8, NULL, i);
+
+       syslog(LOG_NOTICE, "klogd started: %s", bb_banner);
+
+       /* Note: this code does not detect incomplete messages
+        * (messages not ending with '\n' or just when kernel
+        * generates too many messages for us to keep up)
+        * and will split them in two separate lines */
+       while (1) {
+               int n;
+               int priority;
+
+               n = klogctl(2, log_buffer, KLOGD_LOGBUF_SIZE - 1);
+               if (n < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       syslog(LOG_ERR, "klogd: error from klogctl(2): %d - %m",
+                                       errno);
+                       break;
+               }
+               log_buffer[n] = '\n';
+               i = 0;
+               while (i < n) {
+                       priority = LOG_INFO;
+                       start = &log_buffer[i];
+                       if (log_buffer[i] == '<') {
+                               i++;
+                               // kernel never ganerates multi-digit prios
+                               //priority = 0;
+                               //while (log_buffer[i] >= '0' && log_buffer[i] <= '9') {
+                               //      priority = priority * 10 + (log_buffer[i] - '0');
+                               //      i++;
+                               //}
+                               if (isdigit(log_buffer[i])) {
+                                       priority = (log_buffer[i] - '0');
+                                       i++;
+                               }
+                               if (log_buffer[i] == '>')
+                                       i++;
+                               start = &log_buffer[i];
+                       }
+                       while (log_buffer[i] != '\n')
+                               i++;
+                       log_buffer[i] = '\0';
+                       syslog(priority, "%s", start);
+                       i++;
+               }
+       }
+
+       return EXIT_FAILURE;
+}
diff --git a/sysklogd/logger.c b/sysklogd/logger.c
new file mode 100644 (file)
index 0000000..0907501
--- /dev/null
@@ -0,0 +1,176 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini logger implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#ifndef CONFIG_SYSLOGD
+#define SYSLOG_NAMES
+#define SYSLOG_NAMES_CONST
+#include <syslog.h>
+#else
+/* brokenness alert. Everybody except dietlibc get's this wrong by neither
+ * providing a typedef nor an extern for facilitynames and prioritynames
+ * in syslog.h.
+ */
+# include <syslog.h>
+# ifndef __dietlibc__
+/* We have to do this since the header file does neither provide a sane type
+ * for this structure nor extern definitions.  Argh.... bad libc, bad, bad...
+ */
+typedef struct _code {
+       char *c_name; /* FIXME: this should be const char *const c_name ! */
+       int c_val;
+} CODE;
+#  ifdef __UCLIBC__
+extern const CODE prioritynames[];
+extern const CODE facilitynames[];
+#  else
+extern CODE prioritynames[];
+extern CODE facilitynames[];
+#  endif
+# endif
+#endif
+
+/* Decode a symbolic name to a numeric value
+ * this function is based on code
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California.  All rights reserved.
+ *
+ * Original copyright notice is retained at the end of this file.
+ */
+static int decode(char *name, 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));
+}
+
+
+int logger_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int logger_main(int argc, char **argv)
+{
+       char *str_p, *str_t;
+       int i = 0;
+       char name[80];
+
+       /* Fill out the name string early (may be overwritten later) */
+       bb_getpwuid(name, sizeof(name), geteuid());
+       str_t = name;
+
+       /* Parse any options */
+       getopt32(argv, "p:st:", &str_p, &str_t);
+
+       if (option_mask32 & 0x2) /* -s */
+               i |= LOG_PERROR;
+       //if (option_mask32 & 0x4) /* -t */
+       openlog(str_t, i, 0);
+       i = LOG_USER | LOG_NOTICE;
+       if (option_mask32 & 0x1) /* -p */
+               i = pencode(str_p);
+
+       argc -= optind;
+       argv += optind;
+       if (!argc) {
+#define strbuf bb_common_bufsiz1
+               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;
+}
+
+
+/*-
+ * 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..6f4429f
--- /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
+
+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) ATTRIBUTE_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 ATTRIBUTE_UNUSED)
+{
+       signal(SIGINT, SIG_IGN);
+       shmdt(shbuf);
+       exit(0);
+}
+
+int logread_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int logread_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int 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..371b558
--- /dev/null
@@ -0,0 +1,683 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini syslogd implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org>
+ *
+ * "circular buffer" Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com>
+ *
+ * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "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
+
+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_remote     ,) // -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_remote     )) + 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 */
+#define KEY_ID ((long)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(%lx, %d,...)\n", 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)
+{
+       struct flock fl;
+       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) {
+               if (!now)
+                       now = time(NULL);
+               if (G.last_log_time != now) {
+                       G.last_log_time = now; /* reopen log file every second */
+                       close(G.logFD);
+                       goto reopen;
+               }
+       } else {
+ reopen:
+               G.logFD = device_open(G.logFilePath, O_WRONLY | O_CREAT
+                                       | O_NOCTTY | O_APPEND | O_NONBLOCK);
+               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
+       }
+
+       fl.l_whence = SEEK_SET;
+       fl.l_start = 0;
+       fl.l_len = 1;
+       fl.l_type = F_WRLCK;
+       fcntl(G.logFD, F_SETLKW, &fl);
+
+#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);
+                               xrename(oldFile, newFile);
+                       }
+                       /* newFile == "f.0" now */
+                       xrename(G.logFilePath, newFile);
+                       fl.l_type = F_UNLCK;
+                       fcntl(G.logFD, F_SETLKW, &fl);
+                       close(G.logFD);
+                       goto reopen;
+               }
+               ftruncate(G.logFD, 0);
+       }
+       G.curFileSize +=
+#endif
+                       full_write(G.logFD, msg, len);
+       fl.l_type = F_UNLCK;
+       fcntl(G.logFD, F_SETLKW, &fl);
+}
+
+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)
+{
+       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);
+       }
+}
+
+static void quit_signal(int sig)
+{
+       timestamp_and_log_internal("syslogd exiting");
+       puts("syslogd exiting");
+       if (ENABLE_FEATURE_IPC_SYSLOG)
+               ipcsyslog_cleanup();
+       kill_myself_with_sig(sig);
+}
+
+#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) ATTRIBUTE_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 */
+       bb_signals(0
+               + (1 << SIGINT)
+               + (1 << SIGTERM)
+               + (1 << SIGQUIT)
+               , quit_signal);
+       signal(SIGHUP, SIG_IGN);
+       /* signal(SIGCHLD, SIG_IGN); - why? */
+#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);
+
+       for (;;) {
+               size_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 = safe_read(sock_fd, recvbuf, MAX_READ - 1);
+               if (sz < 0)
+                       bb_perror_msg_and_die("read from /dev/log");
+
+               /* 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);
+               }
+       } /* for (;;) */
+}
+
+int syslogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int syslogd_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       char OPTION_DECL;
+
+       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 */
+       getopt32(argv, OPTION_STR, OPTION_PARAM);
+#ifdef SYSLOGD_MARK
+       if (option_mask32 & OPT_mark) // -m
+               G.markInterval = xatou_range(opt_m, 0, INT_MAX/60) * 60;
+#endif
+       //if (option_mask32 & OPT_nofork) // -n
+       //if (option_mask32 & OPT_outfile) // -O
+       if (option_mask32 & OPT_loglevel) // -l
+               G.logLevel = xatou_range(opt_l, 1, 8);
+       //if (option_mask32 & OPT_small) // -S
+#if ENABLE_FEATURE_ROTATE_LOGFILE
+       if (option_mask32 & OPT_filesize) // -s
+               G.logFileSize = xatou_range(opt_s, 0, INT_MAX/1024) * 1024;
+       if (option_mask32 & 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 && !(option_mask32 & OPT_remotelog))
+               option_mask32 |= OPT_locallog;
+
+       /* Store away localhost's name before the fork */
+       G.hostname = safe_gethostname();
+       *strchrnul(G.hostname, '.') = '\0';
+
+       if (!(option_mask32 & OPT_nofork)) {
+               bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
+       }
+       umask(0);
+       write_pidfile("/var/run/syslogd.pid");
+       do_syslogd();
+       /* return EXIT_SUCCESS; */
+}
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..9165741
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# awk tests.
+# Copyright 2007 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 "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"
+
+exit $FAILCOUNT
diff --git a/testsuite/basename/basename-does-not-remove-identical-extension b/testsuite/basename/basename-does-not-remove-identical-extension
new file mode 100644 (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..6b49f92
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+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"
+}
+
+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
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..16fa3c2
--- /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/cp/cp-a-files-to-dir b/testsuite/cp/cp-a-files-to-dir
new file mode 100644 (file)
index 0000000..39f8f81
--- /dev/null
@@ -0,0 +1,14 @@
+echo file number one > file1
+echo file number two > file2
+ln -s file2 link1
+mkdir dir1
+touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3
+mkdir there
+busybox cp -a file1 file2 link1 dir1 there
+test -f there/file1
+test -f there/file2
+test ! -s there/dir1/file3
+test -L there/link1
+test xfile2 = x`readlink there/link1`
+test ! dir1/file3 -ot there/dir1/file3
+test ! dir1/file3 -nt there/dir1/file3
diff --git a/testsuite/cp/cp-a-preserves-links b/testsuite/cp/cp-a-preserves-links
new file mode 100644 (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-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..ce11bfa
--- /dev/null
@@ -0,0 +1,6 @@
+touch foo
+chmod a-r foo
+set +e
+busybox cp foo bar
+set -e
+test ! -f bar
diff --git a/testsuite/cp/cp-files-to-dir b/testsuite/cp/cp-files-to-dir
new file mode 100644 (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/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..ec3a067
--- /dev/null
@@ -0,0 +1,2 @@
+test x"`date -R`" = x"`busybox date -R`"
+
diff --git a/testsuite/date/date-format-works b/testsuite/date/date-format-works
new file mode 100644 (file)
index 0000000..f28d06c
--- /dev/null
@@ -0,0 +1 @@
+test x"`date +%d/%m/%y`" = x"`busybox date +%d/%m/%y`"
diff --git a/testsuite/date/date-u-works b/testsuite/date/date-u-works
new file mode 100644 (file)
index 0000000..7d9902a
--- /dev/null
@@ -0,0 +1,2 @@
+test x"`date -u`" = x"`busybox date -u`"
+
diff --git a/testsuite/date/date-works b/testsuite/date/date-works
new file mode 100644 (file)
index 0000000..2f6dd1e
--- /dev/null
@@ -0,0 +1,2 @@
+test x"`date`" = x"`busybox date`"
+
diff --git a/testsuite/dd/dd-accepts-if b/testsuite/dd/dd-accepts-if
new file mode 100644 (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/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..82041ab
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+du -h "$d" > logfile.gnu
+busybox du -h "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/du/du-k-works b/testsuite/du/du-k-works
new file mode 100644 (file)
index 0000000..177a1a2
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+du -k "$d" > logfile.gnu
+busybox du -k "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/du/du-l-works b/testsuite/du/du-l-works
new file mode 100644 (file)
index 0000000..61e9140
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+du -l "$d" > logfile.gnu
+busybox du -l "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/du/du-m-works b/testsuite/du/du-m-works
new file mode 100644 (file)
index 0000000..bc97073
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+du -m "$d" > logfile.gnu
+busybox du -m "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/du/du-s-works b/testsuite/du/du-s-works
new file mode 100644 (file)
index 0000000..f0b3bf0
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+du -s "$d" > logfile.gnu
+busybox du -s "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/du/du-works b/testsuite/du/du-works
new file mode 100644 (file)
index 0000000..47949c6
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+du "$d" > logfile.gnu
+busybox du "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/echo/echo-does-not-print-newline b/testsuite/echo/echo-does-not-print-newline
new file mode 100644 (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/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-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..bb682db
--- /dev/null
@@ -0,0 +1,86 @@
+#!/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" ""
+
+testing "grep (no newline at EOL)" "grep bug" "bug" "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"
+
+# This doesn't match GNU behaviour (Binary file input matches)
+# acts like GNU grep -a
+testing "grep handles binary files" "grep foo input" "foo\n" "\0foo\n\n" ""
+# This doesn't match GNU behaviour (Binary file (standard input) matches)
+# acts like GNU grep -a
+testing "grep handles binary stdin" "grep foo" "foo\n" "" "\0foo\n\n"
+
+testing "grep matches NUL" "grep . input > /dev/null 2>&1 ; echo \$?" \
+       "0\n" "\0\n" ""
+
+# -e regex
+testing "grep handles multiple regexps" "grep -e one -e two input ; echo \$?" \
+       "one\ntwo\n0\n" "one\ntwo\n" ""
+testing "grep -F handles multiple expessions" "grep -F -e one -e two input ; echo \$?" \
+       "one\ntwo\n0\n" "one\ntwo\n" ""
+
+optional FEATURE_GREP_EGREP_ALIAS
+testing "grep -E supports extended regexps" "grep -E fo+" "foo\n" "" \
+       "b\ar\nfoo\nbaz"
+testing "grep is also egrep" "egrep foo" "foo\n" "" "foo\nbar\n"
+testing "egrep is not case insensitive" \
+       "egrep foo ; [ \$? -ne 0 ] && echo yes" "yes\n" "" "FOO\n"
+
+exit $FAILCOUNT
diff --git a/testsuite/gunzip.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..8ad484f
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+ls -1 "$d" > logfile.gnu
+busybox ls -1 "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/ls/ls-h-works b/testsuite/ls/ls-h-works
new file mode 100644 (file)
index 0000000..7331262
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+ls -h "$d" > logfile.gnu
+busybox ls -h "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/ls/ls-l-works b/testsuite/ls/ls-l-works
new file mode 100644 (file)
index 0000000..efc2b19
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+LC_ALL=C ls -l "$d" > logfile.gnu
+busybox ls -l "$d" > logfile.bb
+diff -w logfile.gnu logfile.bb
diff --git a/testsuite/ls/ls-s-works b/testsuite/ls/ls-s-works
new file mode 100644 (file)
index 0000000..6c8bf36
--- /dev/null
@@ -0,0 +1,4 @@
+[ -n "$d" ] || d=..
+LC_ALL=C ls -1s "$d" > logfile.gnu
+busybox ls -1s "$d" > logfile.bb
+cmp logfile.gnu logfile.bb
diff --git a/testsuite/md5sum/md5sum-verifies-non-binary-file b/testsuite/md5sum/md5sum-verifies-non-binary-file
new file mode 100644 (file)
index 0000000..8566a23
--- /dev/null
@@ -0,0 +1,3 @@
+touch foo
+md5sum foo > bar
+busybox md5sum -c bar
diff --git a/testsuite/mkdir/mkdir-makes-a-directory b/testsuite/mkdir/mkdir-makes-a-directory
new file mode 100644 (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/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/pidof.tests b/testsuite/pidof.tests
new file mode 100755 (executable)
index 0000000..29cfa94
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# pidof tests.
+# Copyright 2005 by Bernhard Fischer
+# Licensed under GPL v2, see file LICENSE for details.
+
+# AUDIT:
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+
+testing "pidof (exit with error)" \
+       "pidof veryunlikelyoccuringbinaryname ; echo \$?" "1\n" "" ""
+testing "pidof (exit with success)" "pidof pidof > /dev/null; echo \$?" \
+       "0\n" "" ""
+# We can get away with this because it says #!/bin/sh up top.
+
+testing "pidof this" "pidof pidof.tests | grep -o -w $$" "$$\n" "" ""
+
+optional FEATURE_PIDOF_SINGLE
+testing "pidof -s" "pidof -s init" "1\n" "" ""
+
+optional FEATURE_PIDOF_OMIT
+# 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/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..fc8392a
--- /dev/null
@@ -0,0 +1,140 @@
+#!/bin/sh
+
+# Usage:
+# runtest [applet1] [applet2...]
+
+# 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
+       local uc_applet=$(echo $applet | tr a-z A-Z)
+       local testname=$(basename $testcase)
+
+       if grep -q "^# CONFIG_${uc_applet} is not set$" $bindir/.config; then
+               echo "UNTESTED: $testname"
+               return 0
+       fi
+
+       if grep -q "^# FEATURE: " $testcase; then
+               local feature=`sed -ne 's/^# FEATURE: //p' $testcase`
+
+               if grep -q "^# ${feature} is not set$" $bindir/.config; then
+                       echo "UNTESTED: $testname"
+                       return 0
+               fi
+       fi
+
+       rm -rf ".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 != 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_applet_tests()
+{
+       local applet=$1
+       local status=0
+       for testcase in $tsdir/$applet/*; do
+               if [ "$testcase" = "$tsdir/$applet/CVS" ]; then
+                       continue
+               fi
+               run_applet_testcase $applet $testcase
+               test $? = 0 || status=1
+       done
+       return $status
+}
+
+
+
+[ -n "$tsdir" ] || tsdir=$(pwd)
+[ -n "$bindir" ] || bindir=$(dirname $(pwd))
+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
+       if [ "$applet" = "links" ]; then continue; fi
+
+       # Any old-style tests for this applet?
+       if [ "$applet" != "CVS" -a -d "$tsdir/$applet" ]; then
+               run_applet_tests "$applet"
+               test $? = 0 || status=1
+       fi
+
+       # Is this a new-style test?
+       if [ -f "${applet}.tests" ]; then
+               if [ ! -h "$LINKSDIR/$applet" ] && [ "${applet:0:4}" != "all_" ]; then
+                       echo "SKIPPED: $applet (not built)"
+                       continue
+               fi
+#              echo "Running test ${tsdir:-.}/${applet}.tests"
+               PATH="$LINKSDIR:$tsdir:$bindir:$PATH" "${tsdir:-.}/${applet}.tests"
+               test $? = 0 || status=1
+       fi
+done
+
+# Leaving the dir makes it somewhat easier to run failed test by hand
+#rm -rf "$LINKSDIR"
+
+if [ $status != 0 -a 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..4cdbaa6
--- /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..ebb44e7
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# SUSv3 compliant seq tests.
+# Copyright 2006 by Rob Landley <rob@landley.net>
+# Licensed under GPL v2, see file LICENSE for details.
+
+# AUDIT: Full SUSv3 coverage (except internationalization).
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+#   file input will be file called "input"
+#   test can create a file "actual" instead of writing to stdout
+
+# Test exit status
+
+testing "seq (exit with error)" "seq 2> /dev/null || echo yes" "yes\n" "" ""
+testing "seq (exit with error)" "seq 1 2 3 4 2> /dev/null || echo yes" \
+       "yes\n" "" ""
+testing "seq one argument" "seq 3" "1\n2\n3\n" "" ""
+testing "seq two arguments" "seq 5 7" "5\n6\n7\n" "" ""
+testing "seq two arguments reversed" "seq 7 5" "" "" ""
+testing "seq two arguments equal" "seq 3 3" "3\n" "" ""
+testing "seq two arguments equal, arbitrary negative step" "seq 1 -15 1" \
+       "1\n" "" ""
+testing "seq two arguments equal, arbitrary positive step" "seq 1 +15 1" \
+       "1\n" "" ""
+testing "seq count up by 2" "seq 4 2 8" "4\n6\n8\n" "" ""
+testing "seq count down by 2" "seq 8 -2 4" "8\n6\n4\n" "" ""
+testing "seq count wrong way #1" "seq 4 -2 8" "" "" ""
+testing "seq count wrong way #2" "seq 8 2 4" "" "" ""
+testing "seq count by .3" "seq 3 .3 4" "3\n3.3\n3.6\n3.9\n" "" ""
+testing "seq count by -.9" "seq .7 -.9 -2.2" "0.7\n-0.2\n-1.1\n-2\n" "" ""
+testing "seq count by zero" "seq 4 0 8 | head -n 10" "" "" ""
+
+exit $FAILCOUNT
diff --git a/testsuite/sort.tests b/testsuite/sort.tests
new file mode 100755 (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/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..0993f03
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# unit test for sum.
+# Copyright 2007 by Bernhard 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..e5b260c
--- /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..64e6d88
--- /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/taskset.tests b/testsuite/taskset.tests
new file mode 100755 (executable)
index 0000000..a3757ab
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Copyright 2006 Bernhard 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..351d355
--- /dev/null
@@ -0,0 +1,26 @@
+#!/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
+
+# Need to call 'busybox test', otherwise shell builtin is used
+
+testing "test ! a = b -a ! c = c: should be false" \
+       "busybox test ! a = b -a ! c = c; echo \$?" \
+       "1\n" \
+       "" \
+       "" \
+
+testing "test ! a = b -a ! c = d: should be true" \
+       "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..94e90d7
--- /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 "optionflag"
+
+# The following environment variables may be set to enable optional behavior
+# in "testing":
+#    VERBOSE - Print the diff -u of each failed test case.
+#    DEBUG - Enable command tracing.
+#    SKIP - do not perform this test (this is set by "optionflag")
+#
+# The "testing" function takes five arguments:
+#      $1) Description to display when running command
+#      $2) Command line arguments to command
+#      $3) Expected result (on stdout)
+#      $4) Data written to file "input"
+#      $5) Data written to stdin
+#
+# The exit value of testing is the exit value of the command it ran.
+#
+# The environment variable "FAILCOUNT" contains a cumulative total of the
+# number of failed tests.
+
+# The "optional" function is used to skip certain tests, ala:
+#   optionflag CONFIG_FEATURE_THINGY
+#
+# The "optional" function checks the environment variable "OPTIONFLAGS",
+# which is either empty (in which case it always clears SKIP) or
+# else contains a colon-separated list of features (in which case the function
+# clears SKIP if the flag was found, or sets it to 1 if the flag was not found).
+
+export FAILCOUNT=0
+export SKIP=
+
+# Helper functions
+
+optional()
+{
+  option=`echo "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"`
+  # Not set?
+  if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ]
+  then
+    SKIP=""
+    return
+  fi
+  SKIP=1
+}
+
+# The testing function
+
+testing()
+{
+  NAME="$1"
+  [ -z "$1" ] && NAME=$2
+
+  if [ $# -ne 5 ]
+  then
+    echo "Test $NAME has wrong number of arguments (must be 5) ($# $*)" >&2
+    exit
+  fi
+
+  [ -n "$DEBUG" ] && set -x
+
+  if [ -n "$SKIP" ]
+  then
+    echo "SKIPPED: $NAME"
+    return 0
+  fi
+
+  echo -ne "$3" > expected
+  echo -ne "$4" > input
+  [ -z "$VERBOSE" ] || echo "echo '$5' | $2"
+  echo -ne "$5" | eval "$2" > actual
+  RETVAL=$?
+
+  cmp expected actual >/dev/null 2>/dev/null
+  if [ $? -ne 0 ]
+  then
+    FAILCOUNT=$[$FAILCOUNT+1]
+    echo "FAIL: $NAME"
+    [ -n "$VERBOSE" ] && diff -u expected actual
+  else
+    echo "PASS: $NAME"
+  fi
+  rm -f input expected actual
+
+  [ -n "$DEBUG" ] && set +x
+
+  return $RETVAL
+}
+
+# Recursively grab an executable and all the libraries needed to run it.
+# Source paths beginning with / will be copied into destpath, otherwise
+# the file is assumed to already be there and only its library dependencies
+# are copied.
+
+mkchroot()
+{
+  [ $# -lt 2 ] && return
+
+  echo -n .
+
+  dest=$1
+  shift
+  for i in "$@"
+  do
+    [ "${i:0:1}" == "/" ] || i=$(which $i)
+    [ -f "$dest/$i" ] && continue
+    if [ -e "$i" ]
+    then
+      d=`echo "$i" | grep -o '.*/'` &&
+      mkdir -p "$dest/$d" &&
+      cat "$i" > "$dest/$i" &&
+      chmod +x "$dest/$i"
+    else
+      echo "Not found: $i"
+    fi
+    mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ')
+  done
+}
+
+# Set up a chroot environment and run commands within it.
+# Needed commands listed on command line
+# Script fed to stdin.
+
+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/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/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..49d4bed
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+# SUSv3 compliant uniq tests.
+# Copyright 2005 by Rob Landley <rob@landley.net>
+# Licensed under GPL v2, see file LICENSE for details.
+
+# AUDIT: Full SUSv3 coverage (except internationalization).
+
+. testing.sh
+
+# testing "test name" "options" "expected result" "file input" "stdin"
+#   file input will be file called "input"
+#   test can create a file "actual" instead of writing to stdout
+
+# Test exit status
+
+testing "uniq (exit with error)" "uniq nonexistent 2> /dev/null || echo yes" \
+       "yes\n" "" ""
+testing "uniq (exit success)" "uniq /dev/null && echo yes" "yes\n" "" ""
+
+# Test various data sources and destinations
+
+testing "uniq (default to stdin)" "uniq" "one\ntwo\nthree\n" "" \
+       "one\ntwo\ntwo\nthree\nthree\nthree\n"
+testing "uniq - (specify stdin)" "uniq -" "one\ntwo\nthree\n" "" \
+       "one\ntwo\ntwo\nthree\nthree\nthree\n"
+testing "uniq input (specify file)" "uniq input" "one\ntwo\nthree\n" \
+       "one\ntwo\ntwo\nthree\nthree\nthree\n" ""
+
+testing "uniq input outfile (two files)" "uniq input actual > /dev/null" \
+       "one\ntwo\nthree\n" "one\ntwo\ntwo\nthree\nthree\nthree\n" ""
+testing "uniq (stdin) outfile" "uniq - actual" \
+       "one\ntwo\nthree\n" "" "one\ntwo\ntwo\nthree\nthree\nthree\n"
+# Note: SUSv3 doesn't seem to require support for "-" output, but we do anyway.
+testing "uniq input - (specify stdout)" "uniq input -" \
+       "one\ntwo\nthree\n" "one\ntwo\ntwo\nthree\nthree\nthree\n" ""
+
+
+#-f skip fields
+#-s skip chars
+#-c occurrences
+#-d dups only
+#-u
+
+# Test various command line options
+
+# Leading whitespace is a minor technical violation of the spec,
+# but since gnu does it...
+testing "uniq -c (occurrence count)" "uniq -c | sed 's/^[ \t]*//'" \
+       "1 one\n2 two\n3 three\n" "" \
+       "one\ntwo\ntwo\nthree\nthree\nthree\n"
+testing "uniq -d (dups only) " "uniq -d" "two\nthree\n" "" \
+       "one\ntwo\ntwo\nthree\nthree\nthree\n"
+
+testing "uniq -f -s (skip fields and chars)" "uniq -f2 -s 3" \
+"cc    dd      ee8
+aa     bb      cc9
+" "" \
+"cc    dd      ee8
+bb     cc      dd8
+aa     bb      cc9
+"
+
+# -d is "Suppress the writing fo lines that are not repeated in the input."
+# -u is "Suppress the writing of lines that are repeated in the input."
+# Therefore, together this means they should produce no output.
+testing "uniq -u and -d produce no output" "uniq -d -u" "" "" \
+       "one\ntwo\ntwo\nthree\nthree\nthree\n"
+
+exit $FAILCOUNT
diff --git a/testsuite/unzip.tests b/testsuite/unzip.tests
new file mode 100755 (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/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..1f4322b
--- /dev/null
@@ -0,0 +1,811 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Linux System Utilities"
+
+config DMESG
+       bool "dmesg"
+       default n
+       help
+         dmesg is used to examine or control the kernel ring buffer.  When the
+         Linux kernel prints messages to the system log, they are stored in
+         the kernel ring buffer.  You can use dmesg to print the kernel's ring
+         buffer, clear the kernel ring buffer, change the size of the kernel
+         ring buffer, and change the priority level at which kernel messages
+         are also logged to the system console.  Enable this option if you
+         wish to enable the 'dmesg' utility.
+
+config FEATURE_DMESG_PRETTY
+       bool "Pretty dmesg output"
+       default y
+       depends on DMESG
+       help
+         If you wish to scrub the syslog level from the output, say 'Y' here.
+         The syslog level is a string prefixed to every line with the form "<#>".
+
+         With this option you will see:
+           # dmesg
+           Linux version 2.6.17.4 .....
+           BIOS-provided physical RAM map:
+            BIOS-e820: 0000000000000000 - 000000000009f000 (usable)
+
+         Without this option you will see:
+           # dmesg
+           <5>Linux version 2.6.17.4 .....
+           <6>BIOS-provided physical RAM map:
+           <6> BIOS-e820: 0000000000000000 - 000000000009f000 (usable)
+
+config FBSET
+       bool "fbset"
+       default n
+       help
+         fbset is used to show or change the settings of a Linux frame buffer
+         device.  The frame buffer device provides a simple and unique
+         interface to access a graphics display.  Enable this option
+         if you wish to enable the 'fbset' utility.
+
+config FEATURE_FBSET_FANCY
+       bool "Turn on extra fbset options"
+       default n
+       depends on FBSET
+       help
+         This option enables extended fbset options, allowing one to set the
+         framebuffer size, color depth, etc.  interface to access a graphics
+         display.  Enable this option if you wish to enable extended fbset
+         options.
+
+config FEATURE_FBSET_READMODE
+       bool "Turn on fbset readmode support"
+       default n
+       depends on FBSET
+       help
+         This option allows fbset to read the video mode database stored by
+         default as /etc/fb.modes, which can be used to set frame buffer
+         device to pre-defined video modes.
+
+config FDFLUSH
+       bool "fdflush"
+       default n
+       help
+         fdflush is only needed when changing media on slightly-broken
+         removable media drives.  It is used to make Linux believe that a
+         hardware disk-change switch has been actuated, which causes Linux to
+         forget anything it has cached from the previous media.  If you have
+         such a slightly-broken drive, you will need to run fdflush every time
+         you change a disk.  Most people have working hardware and can safely
+         leave this disabled.
+
+config FDFORMAT
+       bool "fdformat"
+       default n
+       help
+         fdformat is used to low-level format a floppy disk.
+
+config FDISK
+       bool "fdisk"
+       default n
+       help
+         The fdisk utility is used to divide hard disks into one or more
+         logical disks, which are generally called partitions.  This utility
+         can be used to list and edit the set of partitions or BSD style
+         'disk slices' that are defined on a hard drive.
+
+config FDISK_SUPPORT_LARGE_DISKS
+       bool "Support over 4GB disks"
+       default y
+       depends on FDISK
+       help
+         Enable this option to support large disks > 4GB.
+
+config FEATURE_FDISK_WRITABLE
+       bool "Write support"
+       default y
+       depends on FDISK
+       help
+         Enabling this option allows you to create or change a partition table
+         and write those changes out to disk.  If you leave this option
+         disabled, you will only be able to view the partition table.
+
+config FEATURE_AIX_LABEL
+       bool "Support AIX disklabels"
+       default n
+       depends on FDISK && FEATURE_FDISK_WRITABLE
+       help
+         Enabling this option allows you to create or change AIX disklabels.
+         Most people can safely leave this option disabled.
+
+config FEATURE_SGI_LABEL
+       bool "Support SGI disklabels"
+       default n
+       depends on FDISK && FEATURE_FDISK_WRITABLE
+       help
+         Enabling this option allows you to create or change SGI disklabels.
+         Most people can safely leave this option disabled.
+
+config FEATURE_SUN_LABEL
+       bool "Support SUN disklabels"
+       default n
+       depends on FDISK && FEATURE_FDISK_WRITABLE
+       help
+         Enabling this option allows you to create or change SUN disklabels.
+         Most people can safely leave this option disabled.
+
+config FEATURE_OSF_LABEL
+       bool "Support BSD disklabels"
+       default n
+       depends on FDISK && FEATURE_FDISK_WRITABLE
+       help
+         Enabling this option allows you to create or change BSD disklabels
+         and define and edit BSD disk slices.
+
+config FEATURE_FDISK_ADVANCED
+       bool "Support expert mode"
+       default n
+       depends on FDISK && FEATURE_FDISK_WRITABLE
+       help
+         Enabling this option allows you to do terribly unsafe things like
+         define arbitrary drive geometry, move the beginning of data in a
+         partition, and similarly evil things.  Unless you have a very good
+         reason you would be wise to leave this disabled.
+
+config FINDFS
+       bool "findfs"
+       default n
+       select VOLUMEID
+       help
+         This is similar to the findfs program that is part of the e2fsprogs
+         package.  However, the e2fsprogs version only support ext2/3.  This
+         version supports those in addition to FAT, swap, and ReiserFS.
+         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 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.
+
+         http://www.pathname.com/fhs/pub/fhs-2.3.html#VARLIBHWCLOCKSTATEDIRECTORYFORHWCLO
+
+config IPCRM
+       bool "ipcrm"
+       default n
+       select FEATURE_SUID
+       help
+         The ipcrm utility allows the removal of System V interprocess
+         communication (IPC) objects and the associated data structures
+         from the system.
+
+config IPCS
+       bool "ipcs"
+       default n
+       select FEATURE_SUID
+       help
+         The ipcs utility is used to provide information on the currently
+         allocated System V interprocess (IPC) objects in the system.
+
+config LOSETUP
+       bool "losetup"
+       default n
+       help
+         losetup is used to associate or detach a loop device with a regular
+         file or block device, and to query the status of a loop device.  This
+         version does not currently support enabling data encryption.
+
+config MDEV
+       bool "mdev"
+       default n
+       help
+         mdev is a mini-udev implementation 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_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 "Routines for detecting label and uuid on common filesystems"
+       default n
+       help
+         TODO
+
+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 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 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 SETARCH
+       bool "setarch"
+       default n
+       help
+         The linux32 utility is used to create a 32bit environment for the
+         specified program (usually a shell).  It only makes sense to have
+         this util on a system that supports both 64bit and 32bit userland
+         (like amd64/x86, ppc64/ppc, sparc64/sparc, etc...).
+
+config SWAPONOFF
+       bool "swaponoff"
+       default n
+       help
+         This option enables both the 'swapon' and the 'swapoff' utilities.
+         Once you have created some swap space using 'mkswap', you also need
+         to enable your swap space with the 'swapon' utility.  The 'swapoff'
+         utility is used, typically at system shutdown, to disable any swap
+         space.  If you are not using any swap space, you can leave this
+         option disabled.
+
+config SWITCH_ROOT
+       bool "switch_root"
+       default n
+       help
+         The switch_root utility is used from initramfs to select a new
+         root device.  Under initramfs, you have to use this instead of
+         pivot_root.  (Stop reading here if you don't care why.)
+
+         Booting with initramfs extracts a gzipped cpio archive into rootfs
+         (which is a variant of ramfs/tmpfs).  Because rootfs can't be moved
+         or unmounted*, pivot_root will not work from initramfs.  Instead,
+         switch_root deletes everything out of rootfs (including itself),
+         does a mount --move that overmounts rootfs with the new root, and
+         then execs the specified init program.
+
+         * Because the Linux kernel uses rootfs internally as the starting
+         and ending point for searching through the kernel's doubly linked
+         list of active mount points.  That's why.
+
+config UMOUNT
+       bool "umount"
+       default n
+       help
+         When you want to remove a mounted filesystem from its current mount point,
+         for example when you are shutting down the system, the 'umount' utility is
+         the tool to use.  If you enabled the 'mount' utility, you almost certainly
+         also want to enable 'umount'.
+
+config FEATURE_UMOUNT_ALL
+       bool "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..b4e0515
--- /dev/null
@@ -0,0 +1,35 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y:=
+lib-$(CONFIG_DMESG)            += dmesg.o
+lib-$(CONFIG_FBSET)            += fbset.o
+lib-$(CONFIG_FDFLUSH)          += freeramdisk.o
+lib-$(CONFIG_FDFORMAT)         += fdformat.o
+lib-$(CONFIG_FDISK)            += fdisk.o
+lib-$(CONFIG_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_MKSWAP)           += mkswap.o
+lib-$(CONFIG_MORE)             += more.o
+lib-$(CONFIG_MOUNT)            += mount.o
+lib-$(CONFIG_PIVOT_ROOT)       += pivot_root.o
+lib-$(CONFIG_RDATE)            += rdate.o
+lib-$(CONFIG_READPROFILE)      += readprofile.o
+lib-$(CONFIG_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/dmesg.c b/util-linux/dmesg.c
new file mode 100644 (file)
index 0000000..9e834ff
--- /dev/null
@@ -0,0 +1,62 @@
+/* vi: set sw=4 ts=4: */
+/*
+ *
+ * dmesg - display/control kernel ring buffer.
+ *
+ * Copyright 2006 Rob Landley <rob@landley.net>
+ * Copyright 2006 Bernhard Fischer <rep.nop@aon.at>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/klog.h>
+#include "libbb.h"
+
+int dmesg_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dmesg_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       int len;
+       char *buf;
+       char *size, *level;
+       int flags = getopt32(argv, "cs:n:", &size, &level);
+
+       if (flags & 4) {
+               if (klogctl(8, NULL, xatoul_range(level, 0, 10)))
+                       bb_perror_msg_and_die("klogctl");
+               return EXIT_SUCCESS;
+       }
+
+       len = (flags & 2) ? xatoul_range(size, 2, INT_MAX) : 16384;
+       buf = xmalloc(len);
+       len = klogctl(3 + (flags & 1), 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..ab7770d
--- /dev/null
@@ -0,0 +1,410 @@
+/* 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 {
+       OPT_CHANGE   = (1 << 0),
+       OPT_INFO     = (1 << 1),
+       OPT_READMODE = (1 << 2),
+       OPT_ALL      = (1 << 9),
+
+       CMD_FB = 1,
+       CMD_DB = 2,
+       CMD_GEOMETRY = 3,
+       CMD_TIMING = 4,
+       CMD_ACCEL = 5,
+       CMD_HSYNC = 6,
+       CMD_VSYNC = 7,
+       CMD_LACED = 8,
+       CMD_DOUBLE = 9,
+/*     CMD_XCOMPAT =     10, */
+       CMD_ALL = 11,
+       CMD_INFO = 12,
+       CMD_CHANGE = 13,
+
+#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
+};
+
+static unsigned g_options;
+
+/* Stuff stolen from the kernel's fb.h */
+#define FB_ACTIVATE_ALL 64
+enum {
+       FBIOGET_VSCREENINFO = 0x4600,
+       FBIOPUT_VSCREENINFO = 0x4601
+};
+struct fb_bitfield {
+       uint32_t offset;                /* beginning of bitfield */
+       uint32_t length;                /* length of bitfield */
+       uint32_t msb_right;             /* !=0: Most significant bit is right */
+};
+struct fb_var_screeninfo {
+       uint32_t xres;                  /* visible resolution */
+       uint32_t yres;
+       uint32_t xres_virtual;          /* virtual resolution */
+       uint32_t yres_virtual;
+       uint32_t xoffset;               /* offset from virtual to visible */
+       uint32_t yoffset;               /* resolution */
+
+       uint32_t bits_per_pixel;
+       uint32_t grayscale;             /* !=0 Graylevels instead of colors */
+
+       struct fb_bitfield red;         /* bitfield in fb mem if true color, */
+       struct fb_bitfield green;       /* else only length is significant */
+       struct fb_bitfield blue;
+       struct fb_bitfield transp;      /* transparency */
+
+       uint32_t nonstd;                /* !=0 Non standard pixel format */
+
+       uint32_t activate;              /* see FB_ACTIVATE_x */
+
+       uint32_t height;                /* height of picture in mm */
+       uint32_t width;                 /* width of picture in mm */
+
+       uint32_t accel_flags;           /* acceleration flags (hints)   */
+
+       /* Timing: All values in pixclocks, except pixclock (of course) */
+       uint32_t pixclock;              /* pixel clock in ps (pico seconds) */
+       uint32_t left_margin;           /* time from sync to picture */
+       uint32_t right_margin;          /* time from picture to sync */
+       uint32_t upper_margin;          /* time from sync to picture */
+       uint32_t lower_margin;
+       uint32_t hsync_len;             /* length of horizontal sync */
+       uint32_t vsync_len;             /* length of vertical sync */
+       uint32_t sync;                  /* see FB_SYNC_x */
+       uint32_t vmode;                 /* see FB_VMODE_x */
+       uint32_t reserved[6];           /* Reserved for future compatibility */
+};
+
+
+static const struct cmdoptions_t {
+       const char name[10];
+       const unsigned char param_count;
+       const unsigned char code;
+} g_cmdoptions[] = {
+       { "-fb", 1, CMD_FB },
+       { "-db", 1, CMD_DB },
+       { "-a", 0, CMD_ALL },
+       { "-i", 0, CMD_INFO },
+       { "-g", 5, CMD_GEOMETRY },
+       { "-t", 7, CMD_TIMING },
+       { "-accel", 1, CMD_ACCEL },
+       { "-hsync", 1, CMD_HSYNC },
+       { "-vsync", 1, CMD_VSYNC },
+       { "-laced", 1, CMD_LACED },
+       { "-double", 1, CMD_DOUBLE },
+       { "-n", 0, CMD_CHANGE },
+#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
+       { "", 0, 0 }
+};
+
+#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 int readmode(struct fb_var_screeninfo *base, const char *fn,
+                                       const char *mode)
+{
+       FILE *f;
+       char buf[256];
+       char *p = buf;
+
+       f = xfopen(fn, "r");
+       while (!feof(f)) {
+               fgets(buf, sizeof(buf), f);
+               p = strstr(buf, "mode ");
+               if (!p && !(p = strstr(buf, "mode\t")))
+                       continue;
+               p = strstr(p + 5, mode);
+               if (!p)
+                       continue;
+               p += strlen(mode);
+               if (!isspace(*p) && (*p != 0) && (*p != '"')
+                               && (*p != '\r') && (*p != '\n'))
+                       continue;       /* almost, but not quite */
+
+               while (!feof(f)) {
+                       fgets(buf, sizeof(buf), f);
+                       p = strstr(buf, "geometry ");
+                       if (p) {
+                               p += 9;
+                               /* FIXME: catastrophic on arches with 64bit ints */
+                               sscanf(p, "%d %d %d %d %d",
+                                       &(base->xres), &(base->yres),
+                                       &(base->xres_virtual), &(base->yres_virtual),
+                                       &(base->bits_per_pixel));
+                       } else if ((p = strstr(buf, "timings "))) {
+                               p += 8;
+                               sscanf(p, "%d %d %d %d %d %d %d",
+                                       &(base->pixclock),
+                                       &(base->left_margin), &(base->right_margin),
+                                       &(base->upper_margin), &(base->lower_margin),
+                                       &(base->hsync_len), &(base->vsync_len));
+                       } else if ((p = strstr(buf, "laced "))) {
+                               //p += 6;
+                               if (strstr(buf, "false")) {
+                                       base->vmode &= ~FB_VMODE_INTERLACED;
+                               } else {
+                                       base->vmode |= FB_VMODE_INTERLACED;
+                               }
+                       } else if ((p = strstr(buf, "double "))) {
+                               //p += 7;
+                               if (strstr(buf, "false")) {
+                                       base->vmode &= ~FB_VMODE_DOUBLE;
+                               } else {
+                                       base->vmode |= FB_VMODE_DOUBLE;
+                               }
+                       } else if ((p = strstr(buf, "vsync "))) {
+                               //p += 6;
+                               if (strstr(buf, "low")) {
+                                       base->sync &= ~FB_SYNC_VERT_HIGH_ACT;
+                               } else {
+                                       base->sync |= FB_SYNC_VERT_HIGH_ACT;
+                               }
+                       } else if ((p = strstr(buf, "hsync "))) {
+                               //p += 6;
+                               if (strstr(buf, "low")) {
+                                       base->sync &= ~FB_SYNC_HOR_HIGH_ACT;
+                               } else {
+                                       base->sync |= FB_SYNC_HOR_HIGH_ACT;
+                               }
+                       } else if ((p = strstr(buf, "csync "))) {
+                               //p += 6;
+                               if (strstr(buf, "low")) {
+                                       base->sync &= ~FB_SYNC_COMP_HIGH_ACT;
+                               } else {
+                                       base->sync |= FB_SYNC_COMP_HIGH_ACT;
+                               }
+                       } else if ((p = strstr(buf, "extsync "))) {
+                               //p += 8;
+                               if (strstr(buf, "false")) {
+                                       base->sync &= ~FB_SYNC_EXT;
+                               } else {
+                                       base->sync |= FB_SYNC_EXT;
+                               }
+                       }
+
+                       if (strstr(buf, "endmode"))
+                               return 1;
+               }
+       }
+       return 0;
+}
+#endif
+
+static inline void setmode(struct fb_var_screeninfo *base,
+                                       struct fb_var_screeninfo *set)
+{
+       if ((int) set->xres > 0)
+               base->xres = set->xres;
+       if ((int) set->yres > 0)
+               base->yres = set->yres;
+       if ((int) set->xres_virtual > 0)
+               base->xres_virtual = set->xres_virtual;
+       if ((int) set->yres_virtual > 0)
+               base->yres_virtual = set->yres_virtual;
+       if ((int) set->bits_per_pixel > 0)
+               base->bits_per_pixel = set->bits_per_pixel;
+}
+
+static inline void showmode(struct fb_var_screeninfo *v)
+{
+       double drate = 0, hrate = 0, vrate = 0;
+
+       if (v->pixclock) {
+               drate = 1e12 / v->pixclock;
+               hrate = drate / (v->left_margin + v->xres + v->right_margin + v->hsync_len);
+               vrate = hrate / (v->upper_margin + v->yres + v->lower_margin + v->vsync_len);
+       }
+       printf("\nmode \"%ux%u-%u\"\n"
+#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);
+}
+
+#ifdef STANDALONE
+int main(int argc, char **argv)
+#else
+int fbset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fbset_main(int argc, char **argv)
+#endif
+{
+       struct fb_var_screeninfo var, varset;
+       int fh, i;
+       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); argc--, argv++) {
+               for (i = 0; g_cmdoptions[i].name[0]; i++) {
+                       if (strcmp(thisarg, g_cmdoptions[i].name))
+                               continue;
+                       if (argc-1 < g_cmdoptions[i].param_count)
+                               bb_show_usage();
+
+                       switch (g_cmdoptions[i].code) {
+                       case CMD_FB:
+                               fbdev = argv[1];
+                               break;
+                       case CMD_DB:
+                               modefile = argv[1];
+                               break;
+                       case CMD_GEOMETRY:
+                               varset.xres = xatou32(argv[1]);
+                               varset.yres = xatou32(argv[2]);
+                               varset.xres_virtual = xatou32(argv[3]);
+                               varset.yres_virtual = xatou32(argv[4]);
+                               varset.bits_per_pixel = xatou32(argv[5]);
+                               break;
+                       case CMD_TIMING:
+                               varset.pixclock = xatou32(argv[1]);
+                               varset.left_margin = xatou32(argv[2]);
+                               varset.right_margin = xatou32(argv[3]);
+                               varset.upper_margin = xatou32(argv[4]);
+                               varset.lower_margin = xatou32(argv[5]);
+                               varset.hsync_len = xatou32(argv[6]);
+                               varset.vsync_len = xatou32(argv[7]);
+                               break;
+                       case CMD_ALL:
+                               g_options |= OPT_ALL;
+                               break;
+                       case CMD_CHANGE:
+                               g_options |= OPT_CHANGE;
+                               break;
+#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
+                       }
+                       argc -= g_cmdoptions[i].param_count;
+                       argv += g_cmdoptions[i].param_count;
+                       break;
+               }
+               if (!g_cmdoptions[i].name[0]) {
+                       if (argc != 1)
+                               bb_show_usage();
+                       mode = *argv;
+                       g_options |= OPT_READMODE;
+               }
+       }
+
+       fh = xopen(fbdev, O_RDONLY);
+       xioctl(fh, FBIOGET_VSCREENINFO, &var);
+       if (g_options & OPT_READMODE) {
+#if !ENABLE_FEATURE_FBSET_READMODE
+               bb_show_usage();
+#else
+               if (!readmode(&var, modefile, mode)) {
+                       bb_error_msg_and_die("unknown video mode '%s'", mode);
+               }
+#endif
+       }
+
+       setmode(&var, &varset);
+       if (g_options & OPT_CHANGE) {
+               if (g_options & OPT_ALL)
+                       var.activate = FB_ACTIVATE_ALL;
+               xioctl(fh, FBIOPUT_VSCREENINFO, &var);
+       }
+       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..eac7b15
--- /dev/null
@@ -0,0 +1,130 @@
+/* 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
+ */
+
+#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 ATTRIBUTE_UNUSED, 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..dcfae96
--- /dev/null
@@ -0,0 +1,2993 @@
+/* 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 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
+
+/* 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 */
+} ATTRIBUTE_PACKED;
+
+static const char unable_to_open[] ALIGN1 = "cannot open %s";
+static const char unable_to_read[] ALIGN1 = "cannot read from %s";
+static const char unable_to_seek[] ALIGN1 = "cannot seek on %s";
+static const char unable_to_write[] ALIGN1 = "cannot write to %s";
+static const char ioctl_error[] ALIGN1 = "BLKGETSIZE ioctl failed on %s";
+static void fdisk_fatal(const char *why) ATTRIBUTE_NORETURN;
+
+enum label_type {
+       label_dos, label_sun, label_sgi, label_aix, label_osf
+};
+
+#define LABEL_IS_DOS   (label_dos == current_label_type)
+
+#if ENABLE_FEATURE_SUN_LABEL
+#define LABEL_IS_SUN   (label_sun == current_label_type)
+#define STATIC_SUN static
+#else
+#define LABEL_IS_SUN   0
+#define STATIC_SUN extern
+#endif
+
+#if ENABLE_FEATURE_SGI_LABEL
+#define LABEL_IS_SGI   (label_sgi == current_label_type)
+#define STATIC_SGI static
+#else
+#define LABEL_IS_SGI   0
+#define STATIC_SGI extern
+#endif
+
+#if ENABLE_FEATURE_AIX_LABEL
+#define LABEL_IS_AIX   (label_aix == current_label_type)
+#define STATIC_AIX static
+#else
+#define LABEL_IS_AIX   0
+#define STATIC_AIX extern
+#endif
+
+#if ENABLE_FEATURE_OSF_LABEL
+#define LABEL_IS_OSF   (label_osf == current_label_type)
+#define STATIC_OSF static
+#else
+#define LABEL_IS_OSF   0
+#define STATIC_OSF extern
+#endif
+
+enum action { fdisk, require, try_only, create_empty_dos, create_empty_sun };
+
+static 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
+};
+
+
+/* Globals */
+
+struct globals {
+       char *line_ptr;
+
+       const char *disk_device;
+       int fd;                         /* the disk */
+       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;
+       enum label_type current_label_type;
+       smallint display_in_cyl_units; // = 1;
+#if ENABLE_FEATURE_OSF_LABEL
+       smallint possibly_osf_label;
+#endif
+
+       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 fd                   (G.fd                  )
+#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 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; \
+} while (0)
+
+
+/* TODO: move to libbb? */
+static ullong bb_BLKGETSIZE_sectors(void)
+{
+       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)
+
+#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(0); /* 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)) /* repeat */;
+       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 */
+
+#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);
+}
+
+/* normally O_RDWR, -l option gives O_RDONLY */
+static int type_open = O_RDWR;
+
+static int ext_index;                   /* the prime extended partition */
+static smallint listing;                /* no aborts for fdisk -l */
+static smallint dos_compatible_flag = 1;
+#if ENABLE_FEATURE_FDISK_WRITABLE
+//static int dos_changed;
+static smallint nowarn;                 /* no warnings for fdisk -l/-s */
+#endif
+
+static unsigned user_cylinders, user_heads, user_sectors;
+static unsigned pt_heads, pt_sectors;
+static unsigned kern_heads, kern_sectors;
+
+static ullong extended_offset;            /* offset of link pointers */
+static ullong total_number_of_sectors;
+
+static void fdisk_fatal(const char *why)
+{
+       if (listing) {
+               close(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(fd, (off64_t)secno, SEEK_SET) == (off64_t) -1)
+               fdisk_fatal(unable_to_seek);
+#else
+       if (secno > MAXINT(off_t)
+        || lseek(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, char *buf)
+{
+       seek_sector(secno);
+       if (write(fd, buf, sector_size) != sector_size)
+               fdisk_fatal(unable_to_write);
+}
+#endif
+
+/* Allocate a buffer and read a partition table sector */
+static void
+read_pte(struct pte *pe, ullong offset)
+{
+       pe->offset = offset;
+       pe->sectorbuffer = xmalloc(sector_size);
+       seek_sector(offset);
+       if (read(fd, pe->sectorbuffer, sector_size) != sector_size)
+               fdisk_fatal(unable_to_read);
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       pe->changed = 0;
+#endif
+       pe->part_table = pe->ext_pointer = NULL;
+}
+
+static unsigned
+get_partition_start(const struct pte *pe)
+{
+       return pe->offset + get_start_sect(pe->part_table);
+}
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+/*
+ * Avoid warning about DOS partitions when no DOS partition was changed.
+ * Here a heuristic "is probably dos partition".
+ * We might also do the opposite and warn in all cases except
+ * for "is probably nondos partition".
+ */
+#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++) /* */;
+
+       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(fd, BLKSSZGET, &arg) == 0)
+                       sector_size = arg;
+               if (sector_size != DEFAULT_SECTOR_SIZE)
+                       printf("Note: sector size is %d (not %d)\n",
+                                  sector_size, DEFAULT_SECTOR_SIZE);
+       }
+}
+
+static void
+get_kernel_geometry(void)
+{
+       struct hd_geometry geometry;
+
+       if (!ioctl(fd, HDIO_GETGEO, &geometry)) {
+               kern_heads = geometry.heads;
+               kern_sectors = geometry.sectors;
+               /* never use geometry.cylinders - it is truncated */
+       }
+}
+
+static void
+get_partition_table_geometry(void)
+{
+       const unsigned char *bufp = (const unsigned char *)MBRbuffer;
+       struct partition *p;
+       int i, h, s, hh, ss;
+       int first = 1;
+       int bad = 0;
+
+       if (!(valid_part_table_flag((char*)bufp)))
+               return;
+
+       hh = ss = 0;
+       for (i = 0; i < 4; i++) {
+               p = pt_offset(bufp, i);
+               if (p->sys_ind != 0) {
+                       h = p->end_head + 1;
+                       s = (p->end_sector & 077);
+                       if (first) {
+                               hh = h;
+                               ss = s;
+                               first = 0;
+                       } else if (hh != h || ss != s)
+                               bad = 1;
+               }
+       }
+
+       if (!first && !bad) {
+               pt_heads = hh;
+               pt_sectors = ss;
+       }
+}
+
+static void
+get_geometry(void)
+{
+       int sec_fac;
+
+       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();
+
+       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;
+}
+
+/*
+ * Read MBR.  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;
+
+       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_SUN_LABEL
+       if (what == create_empty_sun && check_sun_label())
+               return 0;
+#endif
+
+       memset(MBRbuffer, 0, 512);
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       if (what == create_empty_dos)
+               goto got_dos_table;             /* skip reading disk */
+
+       fd = open(disk_device, type_open);
+       if (fd < 0) {
+               fd = open(disk_device, O_RDONLY);
+               if (fd < 0) {
+                       if (what == try_only)
+                               return 1;
+                       fdisk_fatal(unable_to_open);
+               } else
+                       printf("You will not be able to write "
+                               "the partition table\n");
+       }
+
+       if (512 != read(fd, MBRbuffer, 512)) {
+               if (what == try_only)
+                       return 1;
+               fdisk_fatal(unable_to_read);
+       }
+#else
+       fd = open(disk_device, O_RDONLY);
+       if (fd < 0)
+               return 1;
+       if (512 != read(fd, MBRbuffer, 512))
+               return 1;
+#endif
+
+       get_geometry();
+
+       update_units();
+
+#if ENABLE_FEATURE_SUN_LABEL
+       if (check_sun_label())
+               return 0;
+#endif
+
+#if ENABLE_FEATURE_SGI_LABEL
+       if (check_sgi_label())
+               return 0;
+#endif
+
+#if ENABLE_FEATURE_AIX_LABEL
+       if (check_aix_label())
+               return 0;
+#endif
+
+#if ENABLE_FEATURE_OSF_LABEL
+       if (check_osf_label()) {
+               possibly_osf_label = 1;
+               if (!valid_part_table_flag(MBRbuffer)) {
+                       current_label_type = label_osf;
+                       return 0;
+               }
+               printf("This disk has both DOS and BSD magic.\n"
+                        "Give the 'b' command to go to BSD mode.\n");
+       }
+#endif
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+ got_dos_table:
+#endif
+
+       if (!valid_part_table_flag(MBRbuffer)) {
+#if !ENABLE_FEATURE_FDISK_WRITABLE
+               return -1;
+#else
+               switch (what) {
+               case fdisk:
+                       printf("Device contains neither a valid DOS "
+                                 "partition table, nor Sun, SGI or OSF "
+                                 "disklabel\n");
+#ifdef __sparc__
+#if ENABLE_FEATURE_SUN_LABEL
+                       create_sunlabel();
+#endif
+#else
+                       create_doslabel();
+#endif
+                       return 0;
+               case try_only:
+                       return -1;
+               case create_empty_dos:
+#if ENABLE_FEATURE_SUN_LABEL
+               case create_empty_sun:
+#endif
+                       break;
+               default:
+                       bb_error_msg_and_die("internal error");
+               }
+#endif /* FEATURE_FDISK_WRITABLE */
+       }
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+       warn_cylinders();
+#endif
+       warn_geometry();
+
+       for (i = 0; i < 4; i++) {
+               struct pte *pe = &ptes[i];
+
+               if (IS_EXTENDED(pe->part_table->sys_ind)) {
+                       if (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);
+#if ENABLE_FEATURE_FDISK_WRITABLE
+                       pe->changed = 1;
+#endif
+               }
+       }
+
+       return 0;
+}
+
+#if ENABLE_FEATURE_FDISK_WRITABLE
+/*
+ * Print the message MESG, then read an integer between LOW and HIGH (inclusive).
+ * If the user hits Enter, DFLT is returned.
+ * Answers like +10 are interpreted as offsets from BASE.
+ *
+ * There is no default if DFLT is not between LOW and HIGH.
+ */
+static unsigned
+read_int(unsigned low, unsigned dflt, unsigned high, unsigned base, 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(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(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':
+                       close(fd);
+                       bb_putchar('\n');
+                       exit(0);
+               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(buf, "r");
+       if (procf != NULL && fgets(buf, sizeof(buf), procf))
+               is_ide = (!strncmp(buf, "cdrom", 5) ||
+                         !strncmp(buf, "tape", 4));
+       else
+               /* Now when this proc file does not exist, skip the
+                  device when it is read-only. */
+               if (stat(device, &statbuf) == 0)
+                       is_ide = ((statbuf.st_mode & 0222) == 0);
+
+       if (procf)
+               fclose(procf);
+       return is_ide;
+}
+
+
+static void
+trydev(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;
+       fd = open(disk_device, type_open);
+       if (fd >= 0) {
+               gb = get_boot(try_only);
+               if (gb > 0) {   /* I/O error */
+                       close(fd);
+               } else if (gb < 0) { /* no DOS signature */
+                       list_disk_geometry();
+                       if (LABEL_IS_AIX) {
+                               return;
+                       }
+#if ENABLE_FEATURE_OSF_LABEL
+                       if (bsd_trydev(device) < 0)
+#endif
+                               printf("Disk %s doesn't contain a valid "
+                                       "partition table\n", device);
+                       close(fd);
+               } else {
+                       close(fd);
+                       list_table(0);
+#if ENABLE_FEATURE_FDISK_WRITABLE
+                       if (!LABEL_IS_SUN && g_partitions > 4){
+                               delete_partition(ext_index);
+                       }
+#endif
+               }
+       } else {
+               /* Ignore other errors, since we try IDE
+                  and SCSI hard disks which may not be
+                  installed on the system. */
+               if (errno == EACCES) {
+                       printf("Cannot open %s\n", device);
+                       return;
+               }
+       }
+}
+
+/* for fdisk -l: try all things in /proc/partitions
+   that look like a partition name (do not end in a digit) */
+static void
+tryprocpt(void)
+{
+       FILE *procpt;
+       char line[100], ptname[100], devname[120], *s;
+       int ma, mi, sz;
+
+       procpt = fopen_or_warn("/proc/partitions", "r");
+
+       while (fgets(line, sizeof(line), procpt)) {
+               if (sscanf(line, " %d %d %d %[^\n ]",
+                               &ma, &mi, &sz, ptname) != 4)
+                       continue;
+               for (s = ptname; *s; s++)
+                       continue;
+               if (isdigit(s[-1]))
+                       continue;
+               sprintf(devname, "/dev/%s", ptname);
+               trydev(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.
+        */
+       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,
+       };
+
+       INIT_G();
+
+       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
+               type_open = O_RDONLY;
+               if (*argv) {
+                       listing = 1;
+                       do {
+                               trydev(*argv, 1);
+                       } while (*++argv);
+               } else {
+                       /* we don't have device names, */
+                       /* use /proc/partitions instead */
+                       tryprocpt();
+               }
+               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() / 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(fdisk);
+
+       if (LABEL_IS_OSF) {
+               /* OSF label, and no DOS label */
+               printf("Detected an OSF/1 disklabel on %s, entering "
+                       "disklabel mode\n", disk_device);
+               bsd_select();
+               /*Why do we do this?  It seems to be counter-intuitive*/
+               current_label_type = label_dos;
+               /* If we return we may want to make an empty DOS label? */
+       }
+
+       while (1) {
+               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':
+                       close(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..0b9fa2b
--- /dev/null
@@ -0,0 +1,73 @@
+#if ENABLE_FEATURE_AIX_LABEL
+/*
+ * Copyright (C) Andreas Neuper, Sep 1998.
+ *      This file may be redistributed under
+ *      the terms of the GNU Public License.
+ */
+
+typedef struct {
+       unsigned int   magic;        /* expect AIX_LABEL_MAGIC */
+       unsigned int   fillbytes1[124];
+       unsigned int   physical_volume_id;
+       unsigned int   fillbytes2[124];
+} aix_partition;
+
+#define AIX_LABEL_MAGIC         0xc9c2d4c1
+#define AIX_LABEL_MAGIC_SWAPPED 0xc1d4c2c9
+#define AIX_INFO_MAGIC          0x00072959
+#define AIX_INFO_MAGIC_SWAPPED  0x59290700
+
+#define aixlabel ((aix_partition *)MBRbuffer)
+
+
+/*
+  Changes:
+  * 1999-03-20 Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+  *     Internationalization
+  *
+  * 2003-03-20 Phillip Kesling <pkesling@sgi.com>
+  *      Some fixes
+*/
+
+static 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..5a7e632
--- /dev/null
@@ -0,0 +1,1060 @@
+#if ENABLE_FEATURE_OSF_LABEL
+/*
+ * Copyright (c) 1987, 1988 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgment:
+ *      This product includes software developed by the University of
+ *      California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#ifndef BSD_DISKMAGIC
+#define BSD_DISKMAGIC     ((uint32_t) 0x82564557)
+#endif
+
+#ifndef BSD_MAXPARTITIONS
+#define BSD_MAXPARTITIONS 16
+#endif
+
+#define BSD_LINUX_BOOTDIR "/usr/ucb/mdec"
+
+#if defined(i386) || defined(__sparc__) || defined(__arm__) \
+ || defined(__m68k__) || defined(__mips__) || defined(__s390__) \
+ || defined(__sh__) || defined(__x86_64__)
+#define BSD_LABELSECTOR   1
+#define BSD_LABELOFFSET   0
+#elif defined(__alpha__) || defined(__powerpc__) || defined(__ia64__) \
+ || defined(__hppa__)
+#define BSD_LABELSECTOR   0
+#define BSD_LABELOFFSET   64
+#elif defined(__s390__) || defined(__s390x__)
+#define BSD_LABELSECTOR   1
+#define BSD_LABELOFFSET   0
+#else
+#error unknown architecture
+#endif
+
+#define BSD_BBSIZE        8192          /* size of boot area, with label */
+#define BSD_SBSIZE        8192          /* max size of fs superblock */
+
+struct xbsd_disklabel {
+       uint32_t   d_magic;                /* the magic number */
+       int16_t    d_type;                 /* drive type */
+       int16_t    d_subtype;              /* controller/d_type specific */
+       char       d_typename[16];         /* type name, e.g. "eagle" */
+       char       d_packname[16];                 /* pack identifier */
+                       /* disk geometry: */
+       uint32_t   d_secsize;              /* # of bytes per sector */
+       uint32_t   d_nsectors;             /* # of data sectors per track */
+       uint32_t   d_ntracks;              /* # of tracks per cylinder */
+       uint32_t   d_ncylinders;           /* # of data cylinders per unit */
+       uint32_t   d_secpercyl;            /* # of data sectors per cylinder */
+       uint32_t   d_secperunit;           /* # of data sectors per unit */
+       /*
+        * Spares (bad sector replacements) below
+        * are not counted in d_nsectors or d_secpercyl.
+        * Spare sectors are assumed to be physical sectors
+        * which occupy space at the end of each track and/or cylinder.
+        */
+       uint16_t   d_sparespertrack;       /* # of spare sectors per track */
+       uint16_t   d_sparespercyl;         /* # of spare sectors per cylinder */
+       /*
+        * Alternate cylinders include maintenance, replacement,
+        * configuration description areas, etc.
+        */
+       uint32_t   d_acylinders;           /* # of alt. cylinders per unit */
+
+                       /* hardware characteristics: */
+       /*
+        * d_interleave, d_trackskew and d_cylskew describe perturbations
+        * in the media format used to compensate for a slow controller.
+        * Interleave is physical sector interleave, set up by the formatter
+        * or controller when formatting.  When interleaving is in use,
+        * logically adjacent sectors are not physically contiguous,
+        * but instead are separated by some number of sectors.
+        * It is specified as the ratio of physical sectors traversed
+        * per logical sector.  Thus an interleave of 1:1 implies contiguous
+        * layout, while 2:1 implies that logical sector 0 is separated
+        * by one sector from logical sector 1.
+        * d_trackskew is the offset of sector 0 on track N
+        * relative to sector 0 on track N-1 on the same cylinder.
+        * Finally, d_cylskew is the offset of sector 0 on cylinder N
+        * relative to sector 0 on cylinder N-1.
+        */
+       uint16_t   d_rpm;                  /* rotational speed */
+       uint16_t   d_interleave;           /* hardware sector interleave */
+       uint16_t   d_trackskew;            /* sector 0 skew, per track */
+       uint16_t   d_cylskew;              /* sector 0 skew, per cylinder */
+       uint32_t   d_headswitch;           /* head switch time, usec */
+       uint32_t   d_trkseek;              /* track-to-track seek, usec */
+       uint32_t   d_flags;                /* generic flags */
+#define NDDATA 5
+       uint32_t   d_drivedata[NDDATA];    /* drive-type specific information */
+#define NSPARE 5
+       uint32_t   d_spare[NSPARE];        /* reserved for future use */
+       uint32_t   d_magic2;               /* the magic number (again) */
+       uint16_t   d_checksum;             /* xor of data incl. partitions */
+                       /* filesystem and partition information: */
+       uint16_t   d_npartitions;          /* number of partitions in following */
+       uint32_t   d_bbsize;               /* size of boot area at sn0, bytes */
+       uint32_t   d_sbsize;               /* max size of fs superblock, bytes */
+       struct xbsd_partition    {      /* the partition table */
+               uint32_t   p_size;         /* number of sectors in partition */
+               uint32_t   p_offset;       /* starting sector */
+               uint32_t   p_fsize;        /* filesystem basic fragment size */
+               uint8_t    p_fstype;       /* filesystem type, see below */
+               uint8_t    p_frag;         /* filesystem fragments per block */
+               uint16_t   p_cpg;          /* filesystem cylinders per group */
+       } d_partitions[BSD_MAXPARTITIONS]; /* actually may be more */
+};
+
+/* d_type values: */
+#define BSD_DTYPE_SMD           1               /* SMD, XSMD; VAX hp/up */
+#define BSD_DTYPE_MSCP          2               /* MSCP */
+#define BSD_DTYPE_DEC           3               /* other DEC (rk, rl) */
+#define BSD_DTYPE_SCSI          4               /* SCSI */
+#define BSD_DTYPE_ESDI          5               /* ESDI interface */
+#define BSD_DTYPE_ST506         6               /* ST506 etc. */
+#define BSD_DTYPE_HPIB          7               /* CS/80 on HP-IB */
+#define BSD_DTYPE_HPFL          8               /* HP Fiber-link */
+#define BSD_DTYPE_FLOPPY        10              /* floppy */
+
+/* d_subtype values: */
+#define BSD_DSTYPE_INDOSPART    0x8             /* is inside dos partition */
+#define BSD_DSTYPE_DOSPART(s)   ((s) & 3)       /* dos partition number */
+#define BSD_DSTYPE_GEOMETRY     0x10            /* drive params in label */
+
+static const char *const xbsd_dktypenames[] = {
+       "unknown",
+       "SMD",
+       "MSCP",
+       "old DEC",
+       "SCSI",
+       "ESDI",
+       "ST506",
+       "HP-IB",
+       "HP-FL",
+       "type 9",
+       "floppy",
+       0
+};
+
+
+/*
+ * 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':
+                       close(fd);
+                       exit(EXIT_SUCCESS);
+               case 'r':
+                       return;
+               case 's':
+                       xbsd_print_disklabel(1);
+                       break;
+               case 't':
+                       xbsd_change_fstype();
+                       break;
+               case 'u':
+                       change_units();
+                       break;
+               case 'w':
+                       xbsd_write_disklabel();
+                       break;
+#if !defined(__alpha__)
+               case 'x':
+                       xbsd_link_part();
+                       break;
+#endif
+               default:
+                       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) {
+#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:");
+               if (lp->d_flags & BSD_D_REMOVABLE)
+                       printf(" removable");
+               if (lp->d_flags & BSD_D_ECC)
+                       printf(" ecc");
+               if (lp->d_flags & BSD_D_BADSECT)
+                       printf(" badsect");
+               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;
+                       } else
+                               return 0;
+               } else 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(path, O_RDONLY);
+       if (fdb < 0) {
+               perror(path);
+               return 0;
+       }
+       if (read(fdb, ptr, size) < 0) {
+               perror(path);
+               close(fdb);
+               return 0;
+       }
+       printf(" ... %s\n", path);
+       close(fdb);
+       return 1;
+}
+
+static void
+sync_disks(void)
+{
+       printf("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
+
+       if (lseek(fd, sector * SECTOR_SIZE, SEEK_SET) == -1)
+               fdisk_fatal(unable_to_seek);
+       if (BSD_BBSIZE != write(fd, disklabelbuffer, BSD_BBSIZE))
+               fdisk_fatal(unable_to_write);
+
+#if defined(__alpha__)
+       printf("Bootstrap installed on %s\n", disk_device);
+#else
+       printf("Bootstrap installed on %s\n",
+               partname(disk_device, xbsd_part_index+1, 0));
+#endif
+
+       sync_disks();
+}
+
+static void
+xbsd_change_fstype(void)
+{
+       int i;
+
+       i = xbsd_get_part_index(xbsd_dlabel.d_npartitions);
+       xbsd_dlabel.d_partitions[i].p_fstype = read_hex(xbsd_fstypes);
+}
+
+static int
+xbsd_get_part_index(int max)
+{
+       char prompt[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
+
+       if (lseek(fd, sector * SECTOR_SIZE, SEEK_SET) == -1)
+               fdisk_fatal(unable_to_seek);
+       if (BSD_BBSIZE != read(fd, disklabelbuffer, BSD_BBSIZE))
+               fdisk_fatal(unable_to_read);
+
+       memmove(d, &disklabelbuffer[BSD_LABELSECTOR * SECTOR_SIZE + BSD_LABELOFFSET],
+                  sizeof(struct xbsd_disklabel));
+
+       if (d->d_magic != BSD_DISKMAGIC || d->d_magic2 != BSD_DISKMAGIC)
+               return 0;
+
+       for (t = d->d_npartitions; t < BSD_MAXPARTITIONS; t++) {
+               d->d_partitions[t].p_size   = 0;
+               d->d_partitions[t].p_offset = 0;
+               d->d_partitions[t].p_fstype = BSD_FS_UNUSED;
+       }
+
+       if (d->d_npartitions > BSD_MAXPARTITIONS)
+               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);
+       if (lseek(fd, 0, SEEK_SET) == -1)
+               fdisk_fatal(unable_to_seek);
+       if (BSD_BBSIZE != write(fd, disklabelbuffer, BSD_BBSIZE))
+               fdisk_fatal(unable_to_write);
+#else
+       if (lseek(fd, sector * SECTOR_SIZE + BSD_LABELOFFSET, SEEK_SET) == -1)
+               fdisk_fatal(unable_to_seek);
+       if (sizeof(struct xbsd_disklabel) != write(fd, d, sizeof(struct xbsd_disklabel)))
+               fdisk_fatal(unable_to_write);
+#endif
+       sync_disks();
+       return 1;
+}
+
+
+#if !defined(__alpha__)
+static int
+xbsd_translate_fstype(int linux_type)
+{
+       switch (linux_type) {
+       case 0x01: /* DOS 12-bit FAT   */
+       case 0x04: /* DOS 16-bit <32M  */
+       case 0x06: /* DOS 16-bit >=32M */
+       case 0xe1: /* DOS access       */
+       case 0xe3: /* DOS R/O          */
+       case 0xf2: /* DOS secondary    */
+               return BSD_FS_MSDOS;
+       case 0x07: /* OS/2 HPFS        */
+               return BSD_FS_HPFS;
+       default:
+               return BSD_FS_OTHER;
+       }
+}
+
+static void
+xbsd_link_part(void)
+{
+       int k, i;
+       struct partition *p;
+
+       k = get_partition(1, 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..1fce0c1
--- /dev/null
@@ -0,0 +1,892 @@
+#if ENABLE_FEATURE_SGI_LABEL
+
+#define SGI_DEBUG 0
+
+/*
+ * Copyright (C) Andreas Neuper, Sep 1998.
+ *      This file may be modified and redistributed under
+ *      the terms of the GNU Public License.
+ */
+
+#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);
+
+       if (lseek(fd, 0, SEEK_SET) < 0)
+               fdisk_fatal(unable_to_seek);
+       if (write(fd, sgilabel, SECTOR_SIZE) != SECTOR_SIZE)
+               fdisk_fatal(unable_to_write);
+       if (!strncmp((char*)sgilabel->directory[0].vol_file_name, "sgilabel", 8)) {
+               /*
+                * keep this habit of first writing the "sgilabel".
+                * I never tested whether it works without (AN 981002).
+                */
+               sgiinfo *info = fill_sgiinfo();
+               int infostartblock = SGI_SSWAP32(sgilabel->directory[0].vol_file_start);
+               if (lseek(fd, infostartblock*SECTOR_SIZE, SEEK_SET) < 0)
+                       fdisk_fatal(unable_to_seek);
+               if (write(fd, info, SECTOR_SIZE) != SECTOR_SIZE)
+                       fdisk_fatal(unable_to_write);
+               free(info);
+       }
+}
+
+static int
+compare_start(int *x, int *y)
+{
+       /*
+        * sort according to start sectors
+        * and prefers largest partition:
+        * entry zero is entire disk entry
+        */
+       unsigned int i = *x;
+       unsigned int j = *y;
+       unsigned int a = sgi_get_start_sector(i);
+       unsigned int b = sgi_get_start_sector(j);
+       unsigned int c = sgi_get_num_sectors(i);
+       unsigned int d = sgi_get_num_sectors(j);
+
+       if (a == b)
+               return (d > c) ? 1 : (d == c) ? 0 : -1;
+       return (a > b) ? 1 : -1;
+}
+
+
+static int
+verify_sgi(int verbose)
+{
+       int Index[16];      /* list of valid partitions */
+       int sortcount = 0;  /* number of used partitions, i.e. non-zero lengths */
+       int entire = 0, i = 0;
+       unsigned int start = 0;
+       long long gap = 0;      /* count unused blocks */
+       unsigned int lastblock = sgi_get_lastblock();
+
+       clearfreelist();
+       for (i = 0; i < 16; i++) {
+               if (sgi_get_num_sectors(i) != 0) {
+                       Index[sortcount++] = i;
+                       if (sgi_get_sysid(i) == SGI_ENTIRE_DISK) {
+                               if (entire++ == 1) {
+                                       if (verbose)
+                                               printf("More than one entire disk entry present\n");
+                               }
+                       }
+               }
+       }
+       if (sortcount == 0) {
+               if (verbose)
+                       printf("No partitions defined\n");
+               return (lastblock > 0) ? 1 : (lastblock == 0) ? 0 : -1;
+       }
+       qsort(Index, sortcount, sizeof(Index[0]), (void*)compare_start);
+       if (sgi_get_sysid(Index[0]) == SGI_ENTIRE_DISK) {
+               if ((Index[0] != 10) && verbose)
+                       printf("IRIX likes when Partition 11 covers the entire disk\n");
+               if ((sgi_get_start_sector(Index[0]) != 0) && verbose)
+                       printf("The entire disk partition should start "
+                               "at block 0,\n"
+                               "not at diskblock %d\n",
+                               sgi_get_start_sector(Index[0]));
+               if (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(fd, BLKGETSIZE, &longsectors);
+       if (!ioctl(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..fcd3818
--- /dev/null
@@ -0,0 +1,730 @@
+#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
+
+/*
+ * fdisksunlabel.c
+ *
+ * I think this is mostly, or entirely, due to
+ *      Jakub Jelinek (jj@sunsite.mff.cuni.cz), July 1996
+ *
+ * Merged with fdisk for other architectures, aeb, June 1998.
+ *
+ * Sat Mar 20 EST 1999 Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ *      Internationalization
+ */
+
+
+static int sun_other_endian;
+static int scsi_disk;
+static int floppy;
+
+#ifndef IDE0_MAJOR
+#define IDE0_MAJOR 3
+#endif
+#ifndef IDE1_MAJOR
+#define IDE1_MAJOR 22
+#endif
+
+static void
+guess_device_type(void)
+{
+       struct stat bootstat;
+
+       if (fstat(fd, &bootstat) < 0) {
+               scsi_disk = 0;
+               floppy = 0;
+       } else if (S_ISBLK(bootstat.st_mode)
+               && (major(bootstat.st_rdev) == IDE0_MAJOR ||
+                   major(bootstat.st_rdev) == IDE1_MAJOR)) {
+               scsi_disk = 0;
+               floppy = 0;
+       } else if (S_ISBLK(bootstat.st_mode)
+               && major(bootstat.st_rdev) == FLOPPY_MAJOR) {
+               scsi_disk = 0;
+               floppy = 1;
+       } else {
+               scsi_disk = 1;
+               floppy = 0;
+       }
+}
+
+static const 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(fd, SCSI_IOCTL_GET_IDLUN, &id))
+               return NULL;
+
+       sprintf(buffer,
+               "Host: scsi%d Channel: %02d Id: %02d Lun: %02d\n",
+               /* This is very wrong (works only if you have one HBA),
+                  but I haven't found a way how to get hostno
+                  from the current kernel */
+               0,
+               (id[0]>>16) & 0xff,
+               id[0] & 0xff,
+               (id[0]>>8) & 0xff
+       );
+       pfd = fopen("/proc/scsi/scsi", "r");
+       if (!pfd) {
+               return NULL;
+       }
+       while (fgets(buffer2, 2048, pfd)) {
+               if (strcmp(buffer, buffer2))
+                       continue;
+               if (!fgets(buffer2, 2048, pfd))
+                       break;
+               q = strstr(buffer2, "Vendor: ");
+               if (!q)
+                       break;
+               q += 8;
+               vendor = q;
+               q = strstr(q, " ");
+               *q++ = '\0';   /* truncate vendor name */
+               q = strstr(q, "Model: ");
+               if (!q)
+                       break;
+               *q = '\0';
+               q += 7;
+               model = q;
+               q = strstr(q, " Rev: ");
+               if (!q)
+                       break;
+               *q = '\0';
+               for (i = 0; i < 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 int ndiv;
+       int i;
+       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) {
+               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(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;
+       if (lseek(fd, 0, SEEK_SET) < 0)
+               fdisk_fatal(unable_to_seek);
+       if (write(fd, sunlabel, SECTOR_SIZE) != SECTOR_SIZE)
+               fdisk_fatal(unable_to_write);
+}
+#endif /* SUN_LABEL */
diff --git a/util-linux/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..7ef5449
--- /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
+ *                           super-block information
+ *
+ * Sat Oct  9 11:17:11 1993, faith@cs.unc.edu: make exit status conform
+ *                           to that required by fsutil
+ *
+ * Mon Jan  3 11:06:52 1994 - Dr. Wettstein (greg%wind.uucp@plains.nodak.edu)
+ *                           Added support for file system valid flag.  Also
+ *                           added program_version variable and output of
+ *                           program name and version number when program
+ *                           is executed.
+ *
+ * 30.10.94 - added support for v2 filesystem
+ *            (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de)
+ *
+ * 10.12.94  -  added test to prevent checking of mounted fs adapted
+ *              from Theodore Ts'o's (tytso@athena.mit.edu) e2fsck
+ *              program.  (Daniel Quinlan, quinlan@yggdrasil.com)
+ *
+ * 01.07.96  - Fixed the v2 fs stuff to use the right #defines and such
+ *            for modern libcs (janl@math.uio.no, Nicolai Langfeldt)
+ *
+ * 02.07.96  - Added C bit fiddling routines from rmk@ecs.soton.ac.uk
+ *             (Russell King).  He made them for ARM.  It would seem
+ *            that the ARM is powerful enough to do this in C whereas
+ *             i386 and m64k must use assembly to get it fast >:-)
+ *            This should make minix fsck system-independent.
+ *            (janl@math.uio.no, Nicolai Langfeldt)
+ *
+ * 04.11.96  - Added minor fixes from Andreas Schwab to avoid compiler
+ *             warnings.  Added mc68k bitops from
+ *            Joerg Dorchain <dorchain@mpi-sb.mpg.de>.
+ *
+ * 06.11.96  - Added v2 code submitted by Joerg Dorchain, but written by
+ *             Andreas Schwab.
+ *
+ * 1999-02-22 Arkadiusz 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 super-block info
+ *     -m for minix-like "mode not cleared" warnings
+ *     -f force filesystem check even if filesystem marked as valid
+ *
+ * The device may be a block device or a image of one, but this isn't
+ * enforced (but it's not much fun on a character device :-).
+ */
+
+#include <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 };
+
+struct globals {
+       int dev_fd;
+#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 super_block_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)
+#define dev_fd             (G.dev_fd             )
+#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 super_block_buffer (G.super_block_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_super_block *)(super_block_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, (version2 ? MINIX2_INODES_PER_BLOCK \
+                                   : MINIX1_INODES_PER_BLOCK))
+#else
+#define INODE_BLOCKS div_roundup(INODES, 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) ATTRIBUTE_NORETURN;
+static void die(const char *str)
+{
+       if (termios_set)
+               tcsetattr(0, 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;
+
+       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(0);
+       }
+}
+
+/*
+ * 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_super_block(void)
+{
+       /*
+        * Set the state of the filesystem based on whether or not there
+        * are uncorrected errors.  The filesystem valid flag is
+        * unconditionally set if we get this far.
+        */
+       Super.s_state |= MINIX_VALID_FS | 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, super_block_buffer, BLOCK_SIZE))
+               die("cannot write super-block");
+}
+
+static void write_tables(void)
+{
+       write_super_block();
+
+       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, super_block_buffer, BLOCK_SIZE))
+               die("cannot read super block");
+       /* 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 super-block");
+       if (ZONESIZE != 0 || BLOCK_SIZE != 1024)
+               die("only 1k blocks/zones supported");
+       if (IMAPS * BLOCK_SIZE * 8 < INODES + 1)
+               die("bad s_imap_blocks field in super-block");
+       if (ZMAPS * BLOCK_SIZE * 8 < ZONES - FIRSTZONE + 1)
+               die("bad s_zmap_blocks field in super-block");
+}
+
+static void read_tables(void)
+{
+       inode_map = xzalloc(IMAPS * BLOCK_SIZE);
+       zone_map = xzalloc(ZMAPS * BLOCK_SIZE);
+       inode_buffer = xmalloc(INODE_BUFFER_SIZE);
+       inode_count = xmalloc(INODES + 1);
+       zone_count = xmalloc(ZONES);
+       if (IMAPS * BLOCK_SIZE != read(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 ATTRIBUTE_UNUSED, 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");
+       }
+       dev_fd = xopen(device_name, OPT_repair ? O_RDWR : O_RDONLY);
+
+       /*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(0, 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_super_block();
+
+       if (OPT_manual)
+               tcsetattr(0, 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..6ec5cb0
--- /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 */
+       unsigned opt;
+#if ENABLE_GETOPT_LONG
+       int longindex;
+#endif
+       const char *charptr;
+
+       if (quiet_errors) /* No error reporting from getopt(3) */
+               opterr = 0;
+
+       /* Reset getopt(3) (see libbb/getopt32.c for long rant) */
+#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 == EOF)
+                       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(long_options,
+                                       sizeof(long_options[0]) * (long_nr+2));
+                       long_options[long_nr].has_arg = arg_opt;
+                       long_options[long_nr].flag = NULL;
+                       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]));
+               }
+               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, l_arg->data);
+               l_arg = l_arg->link;
+       }
+#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..cdb17ca
--- /dev/null
@@ -0,0 +1,157 @@
+/* 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 <getopt.h>
+#include "libbb.h"
+#include "dump.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+static void bb_dump_addfile(char *name)
+{
+       char *p;
+       FILE *fp;
+       char *buf;
+
+       fp = xfopen(name, "r");
+
+       while ((buf = xmalloc_getline(fp)) != NULL) {
+               p = skip_whitespace(buf);
+
+               if (*p && (*p != '#')) {
+                       bb_dump_add(p);
+               }
+               free(buf);
+       }
+       fclose(fp);
+}
+
+static const char *const add_strings[] = {
+       "\"%07.7_ax \" 16/1 \"%03o \" \"\\n\"",         /* b */
+       "\"%07.7_ax \" 16/1 \"%3_c \" \"\\n\"",         /* c */
+       "\"%07.7_ax \" 8/2 \"  %05u \" \"\\n\"",        /* d */
+       "\"%07.7_ax \" 8/2 \" %06o \" \"\\n\"",         /* o */
+       "\"%07.7_ax \" 8/2 \"   %04x \" \"\\n\"",       /* x */
+};
+
+static const char add_first[] 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)
+{
+       const char *p;
+       int ch;
+#if ENABLE_FEATURE_HEXDUMP_REVERSE
+       FILE *fp;
+       smallint rdump = 0;
+#endif
+
+       bb_dump_vflag = FIRST;
+       bb_dump_length = -1;
+
+       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(add_first);
+                       bb_dump_add(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("\"%08.8_Ax\n\"");
+                       bb_dump_add("\"%08.8_ax  \" 8/1 \"%02x \" \"  \" 8/1 \"%02x \" ");
+                       bb_dump_add("\"  |\" 16/1 \"%_p\" \"|\\n\"");
+               }
+               if (ch == 'e') {
+                       bb_dump_add(optarg);
+               } /* else */
+               if (ch == 'f') {
+                       bb_dump_addfile(optarg);
+               } /* else */
+               if (ch == 'n') {
+                       bb_dump_length = xatoi_u(optarg);
+               } /* else */
+               if (ch == 's') {
+                       bb_dump_skip = xatoul_range_sfx(optarg, 0, LONG_MAX, suffixes);
+               } /* else */
+               if (ch == 'v') {
+                       bb_dump_vflag = ALL;
+               }
+#if ENABLE_FEATURE_HEXDUMP_REVERSE
+               if (ch == 'R') {
+                       rdump = 1;
+               }
+#endif
+       }
+
+       if (!bb_dump_fshead) {
+               bb_dump_add(add_first);
+               bb_dump_add("\"%07.7_ax \" 8/2 \"%04x \" \"\\n\"");
+       }
+
+       argv += optind;
+
+#if !ENABLE_FEATURE_HEXDUMP_REVERSE
+       return bb_dump_dump(argv);
+#else
+       if (!rdump) {
+               return bb_dump_dump(argv);
+       }
+
+       /* -R: reverse of 'hexdump -Cv' */
+       fp = stdin;
+       if (!*argv) {
+               argv--;
+               goto jump_in;
+       }
+
+       do {
+               char *buf;
+               fp = xfopen(*argv, "r");
+ jump_in:
+               while ((buf = xmalloc_getline(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..44522df
--- /dev/null
@@ -0,0 +1,128 @@
+/* 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 <getopt.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 ATTRIBUTE_UNUSED, 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..8d5f63c
--- /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(1);
+                       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..4b5c597
--- /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 ATTRIBUTE_UNUSED, 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(0);
+               }
+               if (flags & flag_sem) {
+                       print_sem(id);
+                       fflush_stdout_and_exit(0);
+               }
+               if (flags & flag_msg) {
+                       print_msg(id);
+                       fflush_stdout_and_exit(0);
+               }
+               bb_show_usage();
+       }
+
+       if (!(flags & (flag_shm | flag_msg | flag_sem)))
+               flags |= flag_msg | flag_shm | flag_sem;
+       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(0);
+}
diff --git a/util-linux/losetup.c b/util-linux/losetup.c
new file mode 100644 (file)
index 0000000..d521b7b
--- /dev/null
@@ -0,0 +1,81 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini losetup implementation for busybox
+ *
+ * Copyright (C) 2002  Matt Kraai.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <getopt.h>
+
+#include "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 mutially 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..f0a8854
--- /dev/null
@@ -0,0 +1,432 @@
+/* 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"
+
+#define ENABLE_FEATURE_MDEV_RENAME_REGEXP 1
+
+struct globals {
+       int root_major, root_minor;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define root_major (G.root_major)
+#define root_minor (G.root_minor)
+
+#define MAX_SYSFS_DEPTH 3 /* prevent infinite loops in /sys symlinks */
+
+/* We use additional 64+ bytes in make_device() */
+#define SCRATCH_SIZE 80
+
+static char *next_field(char *s)
+{
+       char *end = skip_non_whitespace(s);
+       s = skip_whitespace(end);
+       *end = '\0';
+       if (*s == '\0')
+               s = NULL;
+       return s;
+}
+
+/* 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)
+{
+       const char *device_name;
+       int major, minor, type, len;
+       int mode = 0660;
+       uid_t uid = 0;
+       gid_t gid = 0;
+       char *dev_maj_min = path + strlen(path);
+       char *command = NULL;
+       char *alias = NULL;
+
+       /* 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.
+        */
+       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';
+               }
+       }
+
+       /* 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! */
+       type = (strncmp(&path[5], "block/", 6) == 0 ? S_IFBLK : S_IFCHR);
+
+       if (ENABLE_FEATURE_MDEV_CONF) {
+               FILE *fp;
+               char *line, *val, *next;
+               unsigned lineno = 0;
+
+               /* If we have config file, look up user settings */
+               fp = fopen_or_warn("/etc/mdev.conf", "r");
+               if (!fp)
+                       goto end_parse;
+
+               while ((line = xmalloc_getline(fp)) != NULL) {
+                       regmatch_t off[1+9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
+
+                       ++lineno;
+                       trim(line);
+                       if (!line[0])
+                               goto next_line;
+
+                       /* Fields: regex uid:gid mode [alias] [cmd] */
+
+                       /* 1st field: regex to match this device */
+                       next = next_field(line);
+                       {
+                               regex_t match;
+                               int result;
+
+                               /* Is this it? */
+                               xregcomp(&match, line, REG_EXTENDED);
+                               result = regexec(&match, device_name, 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 || off[0].rm_eo != strlen(device_name))
+                                       goto next_line;
+                       }
+
+                       /* This line matches: stop parsing the file
+                        * after parsing the rest of fields */
+
+                       /* 2nd field: uid:gid - device ownership */
+                       if (!next) /* field must exist */
+                               bb_error_msg_and_die("bad line %u", lineno);
+                       val = next;
+                       next = next_field(val);
+                       {
+                               struct passwd *pass;
+                               struct group *grp;
+                               char *str_uid = val;
+                               char *str_gid = strchrnul(val, ':');
+
+                               if (*str_gid)
+                                       *str_gid++ = '\0';
+                               /* Parse UID */
+                               pass = getpwnam(str_uid);
+                               if (pass)
+                                       uid = pass->pw_uid;
+                               else
+                                       uid = strtoul(str_uid, NULL, 10);
+                               /* Parse GID */
+                               grp = getgrnam(str_gid);
+                               if (grp)
+                                       gid = grp->gr_gid;
+                               else
+                                       gid = strtoul(str_gid, NULL, 10);
+                       }
+
+                       /* 3rd field: mode - device permissions */
+                       if (!next) /* field must exist */
+                               bb_error_msg_and_die("bad line %u", lineno);
+                       val = next;
+                       next = next_field(val);
+                       mode = strtoul(val, NULL, 8);
+
+                       /* 4th field (opt): >alias */
+                       if (ENABLE_FEATURE_MDEV_RENAME) {
+                               if (!next)
+                                       break;
+                               if (*next == '>') {
+#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+                                       char *s, *p;
+                                       unsigned i, n;
+#endif
+                                       val = next;
+                                       next = next_field(val);
+#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+                                       /* substitute %1..9 with off[1..9], if any */
+                                       n = 0;
+                                       s = val;
+                                       while (*s && *s++ == '%')
+                                               n++;
+
+                                       p = alias = xzalloc(strlen(val) + n * strlen(device_name));
+                                       s = val + 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(val + 1);
+#endif
+                               }
+                       }
+
+                       /* The rest (opt): command to run */
+                       if (!next)
+                               break;
+                       val = next;
+                       if (ENABLE_FEATURE_MDEV_EXEC) {
+                               const char *s = "@$*";
+                               const char *s2 = strchr(s, *val);
+
+                               if (!s2)
+                                       bb_error_msg_and_die("bad line %u", 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);
+                               }
+                       }
+                       /* end of field parsing */
+                       break; /* we found matching line, stop */
+ next_line:
+                       free(line);
+               } /* end of "while line is read from /etc/mdev.conf" */
+
+               free(line); /* in case we used "break" to get here */
+               fclose(fp);
+       }
+ end_parse:
+
+       if (!delete && sscanf(dev_maj_min, "%u:%u", &major, &minor) == 2) {
+
+               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, uid, gid);
+
+                       if (ENABLE_FEATURE_MDEV_RENAME && alias) {
+                               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);
+                                       }
+                               }
+
+                               /* recreate device_name as a symlink to moved device node */
+                               if (rename(device_name, alias) == 0) {
+                                       symlink(alias, device_name);
+                               }
+
+                               free(alias);
+                       }
+               }
+       }
+
+       if (ENABLE_FEATURE_MDEV_EXEC && command) {
+               /* setenv will leak memory, use putenv/unsetenv/free */
+               char *s = xasprintf("MDEV=%s", device_name);
+               putenv(s);
+               if (system(command) == -1)
+                       bb_perror_msg_and_die("can't run '%s'", command);
+               s[4] = '\0';
+               unsetenv(s);
+               free(s);
+               free(command);
+       }
+
+       if (delete)
+               unlink(device_name);
+}
+
+/* File callback for /sys/ traversal */
+static int fileAction(const char *fileName,
+                      struct stat *statbuf ATTRIBUTE_UNUSED,
+                      void *userData,
+                      int depth ATTRIBUTE_UNUSED)
+{
+       size_t len = strlen(fileName) - 4; /* can't underflow */
+       char *scratch = userData;
+
+       /* len check is for paranoid reasons */
+       if (strcmp(fileName + len, "/dev") || len >= PATH_MAX)
+               return FALSE;
+
+       strcpy(scratch, fileName);
+       scratch[len] = '\0';
+       make_device(scratch, 0);
+
+       return TRUE;
+}
+
+/* Directory callback for /sys/ traversal */
+static int dirAction(const char *fileName ATTRIBUTE_UNUSED,
+                      struct stat *statbuf ATTRIBUTE_UNUSED,
+                      void *userData ATTRIBUTE_UNUSED,
+                      int depth)
+{
+       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 by `cat /lib/firmware/$FIRMWARE > /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, char **argv)
+{
+       char *action;
+       char *env_path;
+       RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
+
+       xchdir("/dev");
+
+       if (argc == 2 && !strcmp(argv[1], "-s")) {
+               /* Scan:
+                * mdev -s
+                */
+               struct stat st;
+
+               xstat("/", &st);
+               root_major = major(st.st_dev);
+               root_minor = minor(st.st_dev);
+
+               recursive_action("/sys/block",
+                       ACTION_RECURSE | ACTION_FOLLOWLINKS,
+                       fileAction, dirAction, temp, 0);
+
+               recursive_action("/sys/class",
+                       ACTION_RECURSE | ACTION_FOLLOWLINKS,
+                       fileAction, dirAction, temp, 0);
+
+       } else {
+               /* Hotplug:
+                * env ACTION=... DEVPATH=... mdev
+                * ACTION can be "add" or "remove"
+                * DEVPATH is like "/block/sda" or "/class/input/mice"
+                */
+               action = getenv("ACTION");
+               env_path = getenv("DEVPATH");
+               if (!action || !env_path)
+                       bb_show_usage();
+
+               snprintf(temp, PATH_MAX, "/sys%s", env_path);
+               if (!strcmp(action, "remove"))
+                       make_device(temp, 1);
+               else if (!strcmp(action, "add")) {
+                       make_device(temp, 0);
+
+                       if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
+                               char *fw = getenv("FIRMWARE");
+                               if (fw)
+                                       load_firmware(fw, temp);
+                       }
+               }
+       }
+
+       if (ENABLE_FEATURE_CLEAN_UP)
+               RELEASE_CONFIG_BUFFER(temp);
+
+       return 0;
+}
diff --git a/util-linux/minix.h b/util-linux/minix.h
new file mode 100644 (file)
index 0000000..e630fe0
--- /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 super-block data on disk
+ */
+struct minix_super_block {
+       uint16_t s_ninodes;
+       uint16_t s_nzones;
+       uint16_t s_imap_blocks;
+       uint16_t s_zmap_blocks;
+       uint16_t s_firstdatazone;
+       uint16_t s_log_zone_size;
+       uint32_t s_max_size;
+       uint16_t s_magic;
+       uint16_t s_state;
+       uint32_t s_zones;
+};
+
+struct minix_dir_entry {
+       uint16_t inode;
+       char name[0];
+};
+
+/* Believe it or not, but mount.h has this one #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..60031a5
--- /dev/null
@@ -0,0 +1,734 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * mkfs.c - make a linux (minix) file-system.
+ *
+ * (C) 1991 Linus Torvalds. This file may be redistributed as per
+ * the Linux copyright.
+ */
+
+/*
+ * DD.MM.YY
+ *
+ * 24.11.91  - Time began. Used the fsck sources to get started.
+ *
+ * 25.11.91  - Corrected some bugs. Added support for ".badblocks"
+ *             The algorithm for ".badblocks" is a bit weird, but
+ *             it should work. Oh, well.
+ *
+ * 25.01.92  - Added the -l option for getting the list of bad blocks
+ *             out of a named file. (Dave Rivers, rivers@ponds.uucp)
+ *
+ * 28.02.92  - Added %-information when using -c.
+ *
+ * 28.02.93  - Added support for other namelengths than the original
+ *             14 characters so that I can test the new kernel routines..
+ *
+ * 09.10.93  - Make exit status conform to that required by fsutil
+ *             (Rik Faith, faith@cs.unc.edu)
+ *
+ * 31.10.93  - Added inode request feature, for backup floppies: use
+ *             32 inodes, for a news partition use more.
+ *             (Scott Heavner, sdh@po.cwru.edu)
+ *
+ * 03.01.94  - Added support for file system valid flag.
+ *             (Dr. Wettstein, greg%wind.uucp@plains.nodak.edu)
+ *
+ * 30.10.94  -  added support for v2 filesystem
+ *             (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de)
+ *
+ * 09.11.94  - Added test to prevent overwrite of mounted fs adapted
+ *             from Theodore Ts'o's (tytso@athena.mit.edu) mke2fs
+ *             program.  (Daniel Quinlan, quinlan@yggdrasil.com)
+ *
+ * 03.20.95  - Clear first 512 bytes of filesystem to make certain that
+ *             the filesystem is not misidentified as a MS-DOS FAT filesystem.
+ *             (Daniel Quinlan, quinlan@yggdrasil.com)
+ *
+ * 02.07.96  -  Added small patch from Russell King to make the program a
+ *             good deal more portable (janl@math.uio.no)
+ *
+ * Usage:  mkfs [-c | -l filename ] [-v] [-nXX] [-iXX] device [size-in-blocks]
+ *
+ *     -c for readability checking (SLOW!)
+ *      -l for getting a list of bad blocks from a file.
+ *     -n for namelength (currently the kernel only uses 14 or 30)
+ *     -i for number of inodes
+ *     -v for v2 filesystem
+ *
+ * The device may be a block device or a image of one, but this isn't
+ * enforced (but it's not much fun on a character device :-).
+ *
+ * Modified for BusyBox by Erik Andersen <andersen@debian.org> --
+ *     removed getopt based parser and added a hand rolled one.
+ */
+
+#include "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
+
+struct globals {
+       int dev_fd;
+
+#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 super_block_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_super_block*)G.super_block_buffer)
+
+#define SB_INODES               (SB.s_ninodes)
+#define SB_IMAPS                (SB.s_imap_blocks)
+#define SB_ZMAPS                (SB.s_zmap_blocks)
+#define SB_FIRSTZONE            (SB.s_firstdatazone)
+#define SB_ZONE_SIZE            (SB.s_log_zone_size)
+#define SB_MAXSIZE              (SB.s_max_size)
+#define SB_MAGIC                (SB.s_magic)
+
+#if !ENABLE_FEATURE_MINIX2
+# define SB_ZONES               (SB.s_nzones)
+# define INODE_BLOCKS           div_roundup(SB_INODES, MINIX1_INODES_PER_BLOCK)
+#else
+# define SB_ZONES               (version2 ? SB.s_zones : SB.s_nzones)
+# define INODE_BLOCKS           div_roundup(SB_INODES, \
+                                version2 ? MINIX2_INODES_PER_BLOCK : MINIX1_INODES_PER_BLOCK)
+#endif
+
+#define INODE_BUFFER_SIZE       (INODE_BLOCKS * BLOCK_SIZE)
+#define NORM_FIRSTZONE          (2 + SB_IMAPS + SB_ZMAPS + INODE_BLOCKS)
+
+/* 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 super block valid. */
+       SB.s_state |= MINIX_VALID_FS;
+       SB.s_state &= ~MINIX_ERROR_FS;
+
+       msg_eol = "seek to 0 failed";
+       xlseek(G.dev_fd, 0, SEEK_SET);
+
+       msg_eol = "cannot clear boot sector";
+       xwrite(G.dev_fd, G.boot_block_buffer, 512);
+
+       msg_eol = "seek to BLOCK_SIZE failed";
+       xlseek(G.dev_fd, BLOCK_SIZE, SEEK_SET);
+
+       msg_eol = "cannot write superblock";
+       xwrite(G.dev_fd, G.super_block_buffer, BLOCK_SIZE);
+
+       msg_eol = "cannot write inode map";
+       xwrite(G.dev_fd, G.inode_map, SB_IMAPS * BLOCK_SIZE);
+
+       msg_eol = "cannot write zone map";
+       xwrite(G.dev_fd, G.zone_map, SB_ZMAPS * BLOCK_SIZE);
+
+       msg_eol = "cannot write inodes";
+       xwrite(G.dev_fd, G.inode_buffer, INODE_BUFFER_SIZE);
+
+       msg_eol = "\n";
+}
+
+static void write_block(int blk, char *buffer)
+{
+       xlseek(G.dev_fd, blk * BLOCK_SIZE, SEEK_SET);
+       xwrite(G.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(G.dev_fd, current_block * BLOCK_SIZE, SEEK_SET);
+       msg_eol = "\n";
+
+       /* Try the read */
+       got = read(G.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 ATTRIBUTE_UNUSED)
+{
+       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(G.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(filename, "r");
+       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.super_block_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 ATTRIBUTE_UNUSED, char **argv)
+{
+       struct mntent *mp;
+       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 */
+       mp = find_mount_point(G.device_name, NULL);
+       if (mp && strcmp(G.device_name, mp->mnt_fsname) == 0)
+               bb_error_msg_and_die("%s is mounted on %s; "
+                               "refusing to make a filesystem",
+                               G.device_name, mp->mnt_dir);
+
+       G.dev_fd = xopen(G.device_name, O_RDWR);
+       if (fstat(G.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/mkswap.c b/util-linux/mkswap.c
new file mode 100644 (file)
index 0000000..bf0d7b0
--- /dev/null
@@ -0,0 +1,128 @@
+/* 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_raw(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;
+               if (strcmp(oldcon, newcon) != 0 && fsetfilecon_raw(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..257f401
--- /dev/null
@@ -0,0 +1,205 @@
+/* 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
+#include <termios.h>
+#endif /* FEATURE_USE_TERMIOS */
+
+
+#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 ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, 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;
+       int terminal_width;
+       int 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(CURRENT_TTY, "r");
+       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..bd5f27b
--- /dev/null
@@ -0,0 +1,1942 @@
+/* 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
+/* For FEATURE_MOUNT_LABEL only */
+#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, int bufsize)
+{
+       struct mntent* ment = getmntent(stream);
+       memcpy(result, ment, sizeof(struct mntent));
+       return result;
+}
+#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:rwanfvsi"
+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),
+};
+
+#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,
+               /* "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"
+               "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
+
+static int resolve_mount_spec(char **fsname)
+{
+       char *tmp = NULL;
+
+#if ENABLE_FEATURE_MOUNT_LABEL
+       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);
+#endif
+
+       if (tmp) {
+               *fsname = tmp;
+               return 1;
+       }
+       return 0;
+}
+
+/* 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 (;;) {
+               int i;
+               char *comma = strchr(options, ',');
+               const char *option_str = mount_option_str;
+
+               if (comma) *comma = '\0';
+
+               // 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(filesystems[i], "r");
+               if (!f) continue;
+
+               while ((buf = xmalloc_getline(f)) != 0) {
+                       if (!strncmp(buf, "nodev", 5) && isspace(buf[5]))
+                               continue;
+                       fs = skip_whitespace(buf);
+                       if (*fs=='#' || *fs=='*' || !*fs) continue;
+
+                       llist_add_to_end(&list, xstrdup(fs));
+                       free(buf);
+               }
+               if (ENABLE_FEATURE_CLEAN_UP) fclose(f);
+       }
+
+       return list;
+}
+
+#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 <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;
+                       if (filteropts) {
+                               args[rc++] = (char *)"-o";
+                               args[rc++] = filteropts;
+                       }
+                       args[rc++] = mp->mnt_fsname;
+                       args[rc++] = mp->mnt_dir;
+                       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 */
+};
+
+
+/*
+ * 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 fd;
+       int pid = fork();
+       if (pid < 0) /* error */
+               return -errno;
+       if (pid > 0) /* parent */
+               return 0;
+       /* child */
+       fd = xopen(bb_dev_null, O_RDWR);
+       dup2(fd, 0);
+       dup2(fd, 1);
+       dup2(fd, 2);
+       while (fd > 2) close(fd--);
+       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 ATTRIBUTE_UNUSED)
+{
+       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
+       int bg = 0;
+#else
+       enum { bg = 0 };
+#endif
+       int soft;
+       int intr;
+       int posix;
+       int nocto;
+       int noac;
+       int nolock;
+       int retry;
+       int tcp;
+       int mountprog;
+       int mountvers;
+       int nfsprog;
+       int nfsvers;
+       int retval;
+
+       find_kernel_nfs_mount_version();
+
+       daemonized = 0;
+       mounthost = NULL;
+       retval = ETIMEDOUT;
+       msock = fsock = -1;
+       mclient = NULL;
+
+       /* NB: hostname, mounthost, filteropts must be free()d prior to return */
+
+       filteropts = xstrdup(filteropts); /* going to trash it later... */
+
+       hostname = xstrdup(mp->mnt_fsname);
+       /* mount_main() guarantees that ':' is there */
+       s = strchr(hostname, ':');
+       pathname = s + 1;
+       *s = '\0';
+       /* Ignore all but first hostname in replicated mounts
+          until they can be fully supported. (mack@sgi.com) */
+       s = strchr(hostname, ',');
+       if (s) {
+               *s = '\0';
+               bb_error_msg("warning: multiple hostnames not supported");
+       }
+
+       server_addr.sin_family = AF_INET;
+       if (!inet_aton(hostname, &server_addr.sin_addr)) {
+               hp = gethostbyname(hostname);
+               if (hp == NULL) {
+                       bb_herror_msg("%s", hostname);
+                       goto fail;
+               }
+               if (hp->h_length > sizeof(struct in_addr)) {
+                       bb_error_msg("got bad hp->h_length");
+                       hp->h_length = sizeof(struct in_addr);
+               }
+               memcpy(&server_addr.sin_addr,
+                               hp->h_addr, hp->h_length);
+       }
+
+       memcpy(&mount_server_addr, &server_addr, sizeof(mount_server_addr));
+
+       /* add IP address to mtab options for use when unmounting */
+
+       if (!mp->mnt_opts) { /* TODO: actually mp->mnt_opts is never NULL */
+               mp->mnt_opts = xasprintf("addr=%s", inet_ntoa(server_addr.sin_addr));
+       } else {
+               char *tmp = xasprintf("%s%saddr=%s", mp->mnt_opts,
+                                       mp->mnt_opts[0] ? "," : "",
+                                       inet_ntoa(server_addr.sin_addr));
+               free(mp->mnt_opts);
+               mp->mnt_opts = tmp;
+       }
+
+       /* Set default options.
+        * rsize/wsize (and bsize, for ver >= 3) are left 0 in order to
+        * let the kernel decide.
+        * timeo is filled in after we know whether it'll be TCP or UDP. */
+       memset(&data, 0, sizeof(data));
+       data.retrans    = 3;
+       data.acregmin   = 3;
+       data.acregmax   = 60;
+       data.acdirmin   = 30;
+       data.acdirmax   = 60;
+       data.namlen     = NAME_MAX;
+
+       soft = 0;
+       intr = 0;
+       posix = 0;
+       nocto = 0;
+       nolock = 0;
+       noac = 0;
+       retry = 10000;          /* 10000 minutes ~ 1 week */
+       tcp = 0;
+
+       mountprog = MOUNTPROG;
+       mountvers = 0;
+       port = 0;
+       mountport = 0;
+       nfsprog = 100003;
+       nfsvers = 0;
+
+       /* parse options */
+       if (filteropts) for (opt = strtok(filteropts, ","); opt; opt = strtok(NULL, ",")) {
+               char *opteq = strchr(opt, '=');
+               if (opteq) {
+                       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";
+                       int val = xatoi_u(opteq + 1);
+                       *opteq = '\0';
+                       switch (index_in_strings(options, opt)) {
+                       case 0: // "rsize"
+                               data.rsize = val;
+                               break;
+                       case 1: // "wsize"
+                               data.wsize = val;
+                               break;
+                       case 2: // "timeo"
+                               data.timeo = val;
+                               break;
+                       case 3: // "retrans"
+                               data.retrans = val;
+                               break;
+                       case 4: // "acregmin"
+                               data.acregmin = val;
+                               break;
+                       case 5: // "acregmax"
+                               data.acregmax = val;
+                               break;
+                       case 6: // "acdirmin"
+                               data.acdirmin = val;
+                               break;
+                       case 7: // "acdirmax"
+                               data.acdirmax = val;
+                               break;
+                       case 8: // "actimeo"
+                               data.acregmin = val;
+                               data.acregmax = val;
+                               data.acdirmin = val;
+                               data.acdirmax = val;
+                               break;
+                       case 9: // "retry"
+                               retry = val;
+                               break;
+                       case 10: // "port"
+                               port = val;
+                               break;
+                       case 11: // "mountport"
+                               mountport = val;
+                               break;
+                       case 12: // "mounthost"
+                               mounthost = xstrndup(opteq+1,
+                                               strcspn(opteq+1," \t\n\r,"));
+                               break;
+                       case 13: // "mountprog"
+                               mountprog = val;
+                               break;
+                       case 14: // "mountvers"
+                               mountvers = val;
+                               break;
+                       case 15: // "nfsprog"
+                               nfsprog = val;
+                               break;
+                       case 16: // "nfsvers"
+                       case 17: // "vers"
+                               nfsvers = val;
+                               break;
+                       case 18: // "proto"
+                               if (!strncmp(opteq+1, "tcp", 3))
+                                       tcp = 1;
+                               else if (!strncmp(opteq+1, "udp", 3))
+                                       tcp = 0;
+                               else
+                                       bb_error_msg("warning: unrecognized proto= option");
+                               break;
+                       case 19: // "namlen"
+                               if (nfs_mount_version >= 2)
+                                       data.namlen = val;
+                               else
+                                       bb_error_msg("warning: option namlen is not supported\n");
+                               break;
+                       case 20: // "addr" - ignore
+                               break;
+                       default:
+                               bb_error_msg("unknown nfs mount parameter: %s=%d", opt, val);
+                               goto fail;
+                       }
+               }
+               else {
+                       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";
+                       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;
+                       default:
+                               bb_error_msg("unknown nfs mount option: %s%s", val ? "" : "no", opt);
+                               goto fail;
+                       }
+               }
+       }
+       proto = (tcp) ? IPPROTO_TCP : IPPROTO_UDP;
+
+       data.flags = (soft ? NFS_MOUNT_SOFT : 0)
+               | (intr ? NFS_MOUNT_INTR : 0)
+               | (posix ? NFS_MOUNT_POSIX : 0)
+               | (nocto ? NFS_MOUNT_NOCTO : 0)
+               | (noac ? NFS_MOUNT_NOAC : 0);
+       if (nfs_mount_version >= 2)
+               data.flags |= (tcp ? NFS_MOUNT_TCP : 0);
+       if (nfs_mount_version >= 3)
+               data.flags |= (nolock ? NFS_MOUNT_NONLM : 0);
+       if (nfsvers > MAX_NFSPROT || mountvers > MAX_NFSPROT) {
+               bb_error_msg("NFSv%d not supported", nfsvers);
+               goto fail;
+       }
+       if (nfsvers && !mountvers)
+               mountvers = (nfsvers < 3) ? 1 : nfsvers;
+       if (nfsvers && nfsvers < mountvers) {
+               mountvers = nfsvers;
+       }
+
+       /* Adjust options if none specified */
+       if (!data.timeo)
+               data.timeo = tcp ? 70 : 7;
+
+       data.version = nfs_mount_version;
+
+       if (vfsflags & MS_REMOUNT)
+               goto do_mount;
+
+       /*
+        * If the previous mount operation on the same host was
+        * backgrounded, and the "bg" for this mount is also set,
+        * give up immediately, to avoid the initial timeout.
+        */
+       if (bg && we_saw_this_host_before(hostname)) {
+               daemonized = daemonize();
+               if (daemonized <= 0) { /* parent or error */
+                       retval = -daemonized;
+                       goto ret;
+               }
+       }
+
+       /* create mount daemon client */
+       /* See if the nfs host = mount host. */
+       if (mounthost) {
+               if (mounthost[0] >= '0' && mounthost[0] <= '9') {
+                       mount_server_addr.sin_family = AF_INET;
+                       mount_server_addr.sin_addr.s_addr = inet_addr(hostname);
+               } else {
+                       hp = gethostbyname(mounthost);
+                       if (hp == NULL) {
+                               bb_herror_msg("%s", mounthost);
+                               goto fail;
+                       } else {
+                               if (hp->h_length > sizeof(struct in_addr)) {
+                                       bb_error_msg("got bad hp->h_length?");
+                                       hp->h_length = sizeof(struct in_addr);
+                               }
+                               mount_server_addr.sin_family = AF_INET;
+                               memcpy(&mount_server_addr.sin_addr,
+                                               hp->h_addr, hp->h_length);
+                       }
+               }
+       }
+
+       /*
+        * The following loop implements the mount retries. When the mount
+        * times out, and the "bg" option is set, we background ourself
+        * and continue trying.
+        *
+        * The case where the mount point is not present and the "bg"
+        * option is set, is treated as a timeout. This is done to
+        * support nested mounts.
+        *
+        * The "retry" count specified by the user is the number of
+        * minutes to retry before giving up.
+        */
+       {
+               struct timeval total_timeout;
+               struct timeval retry_timeout;
+               struct pmap pm_mnt;
+               time_t t;
+               time_t prevt;
+               time_t timeout;
+
+               retry_timeout.tv_sec = 3;
+               retry_timeout.tv_usec = 0;
+               total_timeout.tv_sec = 20;
+               total_timeout.tv_usec = 0;
+               timeout = time(NULL) + 60 * retry;
+               prevt = 0;
+               t = 30;
+ retry:
+               /* be careful not to use too many CPU cycles */
+               if (t - prevt < 30)
+                       sleep(30);
+
+               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;
+               }
+       }
+
+ do_mount: /* perform actual mount */
+
+       mp->mnt_type = (char*)"nfs";
+       retval = mount_it_now(mp, vfsflags, (char*)&data);
+       goto ret;
+
+ fail: /* abort */
+
+       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]
+       ) {
+               len_and_sockaddr *lsa;
+               char *ip, *dotted;
+               char *s;
+
+               rc = 1;
+               // Replace '/' with '\' and verify that unc points to "//server/share".
+
+               for (s = mp->mnt_fsname; *s; ++s)
+                       if (*s == '/') *s = '\\';
+
+               // get server IP
+
+               s = strrchr(mp->mnt_fsname, '\\');
+               if (s <= mp->mnt_fsname+1) goto report_error;
+               *s = '\0';
+               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);
+
+               // lock is required
+               vfsflags |= MS_MANDLOCK;
+
+               mp->mnt_type = (char*)"cifs";
+               rc = mount_it_now(mp, vfsflags, filteropts);
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       free(mp->mnt_fsname);
+                       free(ip);
+                       free(dotted);
+                       free(lsa);
+               }
+               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;
+}
+
+// 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, char **argv)
+{
+       char *cmdopts = xstrdup("");
+       char *fstype = NULL;
+       char *storage_path = NULL;
+       char *opt_o;
+       const char *fstabname;
+       FILE *fstab;
+       int i, j, rc = 0;
+       unsigned opt;
+       struct mntent mtpair[2], *mtcur = mtpair;
+       SKIP_DESKTOP(const int nonroot = 0;)
+
+       USE_DESKTOP( int nonroot = ) 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 = 0; i < argc; i++) {
+               if (argv[i][0] == '-' && argv[i][1] == '-') {
+                       append_mount_options(&cmdopts, argv[i]+2);
+               } else argv[j++] = argv[i];
+       }
+       argv[j] = NULL;
+       argc = j;
+
+       // Parse remaining options
+
+#if ENABLE_FEATURE_MOUNT_VERBOSE
+       opt_complementary = "vv"; // -v is a counter
+#endif
+       opt = getopt32(argv, OPTION_STR, &opt_o, &fstype
+                       USE_FEATURE_MOUNT_VERBOSE(, &verbose));
+       if (opt & OPT_o) append_mount_options(&cmdopts, opt_o); // -o
+       if (opt & OPT_r) append_mount_options(&cmdopts, "ro"); // -r
+       if (opt & OPT_w) append_mount_options(&cmdopts, "rw"); // -w
+       argv += optind;
+       argc -= optind;
+
+       // Three or more non-option arguments?  Die with a usage message.
+
+       if (argc > 2) bb_show_usage();
+
+       // If we have no arguments, show currently mounted filesystems
+
+       if (!argc) {
+               if (!(opt & OPT_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;
+               }
+       } else storage_path = bb_simplify_path(argv[0]);
+
+       // When we have two arguments, the second is the directory and we can
+       // skip looking at fstab entirely.  We can always abspath() the directory
+       // argument when we get it.
+
+       if (argc == 2) {
+               if (nonroot)
+                       bb_error_msg_and_die(must_be_root);
+               mtpair->mnt_fsname = argv[0];
+               mtpair->mnt_dir = argv[1];
+               mtpair->mnt_type = fstype;
+               mtpair->mnt_opts = cmdopts;
+               if (ENABLE_FEATURE_MOUNT_LABEL) {
+                       resolve_mount_spec(&mtpair->mnt_fsname);
+               }
+               rc = singlemount(mtpair, 0);
+               goto clean_up;
+       }
+
+       i = parse_mount_options(cmdopts, 0); // 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))
+       ) {
+               rc = verbose_mount("", argv[0], "", i, "");
+               if (rc) bb_simple_perror_msg_and_die(argv[0]);
+               goto clean_up;
+       }
+
+       // Open either fstab or mtab
+
+       fstabname = "/etc/fstab";
+       if (i & MS_REMOUNT) {
+               fstabname = bb_path_mtab_file;
+       }
+       fstab = setmntent(fstabname, "r");
+       if (!fstab)
+               bb_perror_msg_and_die("cannot read %s", fstabname);
+
+       // Loop through entries until we find what we're looking for.
+
+       memset(mtpair, 0, sizeof(mtpair));
+       for (;;) {
+               struct mntent *mtnext = (mtcur==mtpair ? mtpair+1 : mtpair);
+
+               // Get next fstab entry
+
+               if (!getmntent_r(fstab, mtcur, getmntent_buf
+                                       + (mtcur==mtpair ? GETMNTENT_BUFSIZE/2 : 0),
+                               GETMNTENT_BUFSIZE/2))
+               {
+                       // Were we looking for something specific?
+
+                       if (argc) {
+
+                               // If we didn't find anything, complain.
+
+                               if (!mtnext->mnt_fsname)
+                                       bb_error_msg_and_die("can't find %s in %s",
+                                               argv[0], fstabname);
+
+                               mtcur = mtnext;
+                               if (nonroot) {
+                                       // fstab must have "users" or "user"
+                                       if (!(parse_mount_options(mtcur->mnt_opts, 0) & MOUNT_USERS))
+                                               bb_error_msg_and_die(must_be_root);
+                               }
+
+                               // Mount the last thing we found.
+
+                               mtcur->mnt_opts = xstrdup(mtcur->mnt_opts);
+                               append_mount_options(&(mtcur->mnt_opts), cmdopts);
+                               if (ENABLE_FEATURE_MOUNT_LABEL) {
+                                       resolve_mount_spec(&mtpair->mnt_fsname);
+                               }
+                               rc = singlemount(mtcur, 0);
+                               free(mtcur->mnt_opts);
+                       }
+                       goto clean_up;
+               }
+
+               /* If we're trying to mount something specific and this isn't it,
+                * skip it.  Note we must match both the exact text in fstab (ala
+                * "proc") or a full path from root */
+
+               if (argc) {
+
+                       // Is this what we're looking for?
+
+                       if (strcmp(argv[0], mtcur->mnt_fsname) &&
+                          strcmp(storage_path, mtcur->mnt_fsname) &&
+                          strcmp(argv[0], mtcur->mnt_dir) &&
+                          strcmp(storage_path, mtcur->mnt_dir)) continue;
+
+                       // Remember this entry.  Something later may have overmounted
+                       // it, and we want the _last_ match.
+
+                       mtcur = mtnext;
+
+               // If we're mounting all.
+
+               } else {
+                       // Do we need to match a filesystem type?
+                       if (fstype && match_fstype(mtcur, fstype))
+                               continue;
+
+                       // Skip noauto and swap anyway.
+
+                       if (parse_mount_options(mtcur->mnt_opts, 0) & (MOUNT_NOAUTO | MOUNT_SWAP))
+                               continue;
+
+                       // No, mount -a won't mount anything,
+                       // even user mounts, for mere humans.
+
+                       if (nonroot)
+                               bb_error_msg_and_die(must_be_root);
+
+                       // Mount this thing.
+                       if (ENABLE_FEATURE_MOUNT_LABEL)
+                               resolve_mount_spec(&mtpair->mnt_fsname);
+
+                       // NFS mounts want this to be xrealloc-able
+                       mtcur->mnt_opts = xstrdup(mtcur->mnt_opts);
+                       if (singlemount(mtcur, 1)) {
+                               /* Count number of failed mounts */
+                               rc++;
+                       }
+                       free(mtcur->mnt_opts);
+               }
+       }
+       if (ENABLE_FEATURE_CLEAN_UP) endmntent(fstab);
+
+ clean_up:
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               free(storage_path);
+               free(cmdopts);
+       }
+
+       return rc;
+}
diff --git a/util-linux/pivot_root.c b/util-linux/pivot_root.c
new file mode 100644 (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..9e7bdba
--- /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 ATTRIBUTE_UNUSED)
+{
+       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 ATTRIBUTE_UNUSED, 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/readprofile.c b/util-linux/readprofile.c
new file mode 100644 (file)
index 0000000..e25d07d
--- /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 ATTRIBUTE_UNUSED, 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_open_read_close(proFile, &len);
+       if (!optNative) {
+               int entries = len/sizeof(*buf);
+               int big = 0, small = 0, i;
+               unsigned *p;
+
+               for (p = buf+1; p < buf+entries; p++) {
+                       if (*p & ~0U << (sizeof(*buf)*4))
+                               big++;
+                       if (*p & ((1 << (sizeof(*buf)*4))-1))
+                               small++;
+               }
+               if (big > small) {
+                       bb_error_msg("assuming reversed byte order, "
+                               "use -n to force native byte order");
+                       for (p = buf; p < buf+entries; p++)
+                               for (i = 0; i < sizeof(*buf)/2; i++) {
+                                       unsigned char *b = (unsigned char *) p;
+                                       unsigned char tmp;
+
+                                       tmp = b[i];
+                                       b[i] = b[sizeof(*buf)-i-1];
+                                       b[sizeof(*buf)-i-1] = tmp;
+                               }
+               }
+       }
+
+       step = buf[0];
+       if (optInfo) {
+               printf("Sampling_step: %i\n", step);
+               return EXIT_SUCCESS;
+       }
+
+       total = 0;
+
+       map = xfopen(mapFile, "r");
+
+       while (fgets(mapline, S_LEN, map)) {
+               if (sscanf(mapline, "%llx %s %s", &fn_add, mode, fn_name) != 3)
+                       bb_error_msg_and_die("%s(%i): wrong map line",
+                                            mapFile, maplineno);
+
+               if (!strcmp(fn_name, "_stext")) /* only elf works like this */ {
+                       add0 = fn_add;
+                       break;
+               }
+               maplineno++;
+       }
+
+       if (!add0)
+               bb_error_msg_and_die("can't find \"_stext\" in %s", mapFile);
+
+       /*
+        * Main loop.
+        */
+       while (fgets(mapline, S_LEN, map)) {
+               unsigned int this = 0;
+
+               if (sscanf(mapline, "%llx %s %s", &next_add, mode, next_name) != 3)
+                       bb_error_msg_and_die("%s(%i): wrong map line",
+                                       mapFile, maplineno);
+
+               header_printed = 0;
+
+               /* ignore any LEADING (before a '[tT]' symbol is found)
+                  Absolute symbols */
+               if ((*mode == 'A' || *mode == '?') && total == 0) continue;
+               if (*mode != 'T' && *mode != 't' &&
+                   *mode != 'W' && *mode != 'w')
+                       break;  /* only text is profiled */
+
+               if (indx >= len / sizeof(*buf))
+                       bb_error_msg_and_die("profile address out of range. "
+                                            "Wrong map file?");
+
+               while (indx < (next_add-add0)/step) {
+                       if (optBins && (buf[indx] || optAll)) {
+                               if (!header_printed) {
+                                       printf("%s:\n", fn_name);
+                                       header_printed = 1;
+                               }
+                               printf("\t%"PRIx64"\t%u\n", (indx - 1)*step + add0, buf[indx]);
+                       }
+                       this += buf[indx++];
+               }
+               total += this;
+
+               if (optBins) {
+                       if (optVerbose || this > 0)
+                               printf("  total\t\t\t\t%u\n", this);
+               } else if ((this || optAll)
+                       && (fn_len = next_add-fn_add) != 0
+               ) {
+                       if (optVerbose)
+                               printf("%016llx %-40s %6i %8.4f\n", fn_add,
+                                      fn_name, this, this/(double)fn_len);
+                       else
+                               printf("%6i %-40s %8.4f\n",
+                                      this, fn_name, this/(double)fn_len);
+                       if (optSub) {
+                               unsigned long long scan;
+
+                               for (scan = (fn_add-add0)/step + 1;
+                                    scan < (next_add-add0)/step; scan++) {
+                                       unsigned long long addr;
+
+                                       addr = (scan - 1)*step + add0;
+                                       printf("\t%#llx\t%s+%#llx\t%u\n",
+                                              addr, fn_name, addr - fn_add,
+                                              buf[scan]);
+                               }
+                       }
+               }
+
+               fn_add = next_add;
+               strcpy(fn_name, next_name);
+
+               maplineno++;
+       }
+
+       /* clock ticks, out of kernel text - probably modules */
+       printf("%6i %s\n", buf[len/sizeof(*buf)-1], "*unknown*");
+
+       /* trailer */
+       if (optVerbose)
+               printf("%016x %-40s %6i %8.4f\n",
+                      0, "total", total, total/(double)(fn_add-add0));
+       else
+               printf("%6i %-40s %8.4f\n",
+                      total, "total", total/(double)(fn_add-add0));
+
+       fclose(map);
+       free(buf);
+
+       return EXIT_SUCCESS;
+}
diff --git a/util-linux/rtcwake.c b/util-linux/rtcwake.c
new file mode 100644 (file)
index 0000000..6df7334
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * rtcwake -- enter a system sleep state until specified wakeup time.
+ *
+ * This version was taken from util-linux and scrubbed down for busybox.
+ *
+ * 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   "suspend"
+
+static time_t rtc_time;
+
+static int 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 0;
+
+       /* 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);
+       }
+}
+
+static void suspend_system(const char *suspend)
+{
+       FILE *f = xfopen(SYS_POWER_PATH, "w");
+       fprintf(f, "%s\n", suspend);
+       fflush(f);
+       /* this executes after wake from suspend */
+       fclose(f);
+}
+
+#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 ATTRIBUTE_UNUSED, 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(0);
+       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"))
+               suspend_system(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..1c95ea5
--- /dev/null
@@ -0,0 +1,187 @@
+/* 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 ATTRIBUTE_UNUSED)
+{
+       fd_count = 0;
+}
+
+int script_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int script_main(int argc ATTRIBUTE_UNUSED, 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 = getpty(pty_line);
+       if (pty < 0) {
+               bb_perror_msg_and_die("can't get pty");
+       }
+
+       /* 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];
+               struct pollfd *ppfd = pfd;
+               int outfd, count, loop;
+
+               outfd = xopen(fname, mode);
+               pfd[0].fd = 0;
+               pfd[0].events = POLLIN;
+               pfd[1].fd = pty;
+               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 */
+                       poll(ppfd, fd_count, -1);
+                       if (pfd[0].revents) {
+                               count = safe_read(0, buf, sizeof(buf));
+                               if (count <= 0) {
+                                       /* err/eof: don't read anymore */
+                                       pfd[0].revents = 0;
+                                       ppfd++;
+                                       fd_count--;
+                               } else {
+                                       full_write(pty, buf, count);
+                               }
+                       }
+                       if (pfd[1].revents) {
+                               errno = 0;
+                               count = safe_read(pty, buf, sizeof(buf));
+                               if (count <= 0 && errno != EAGAIN) {
+                                       /* err/eof: don't read anymore */
+                                       pfd[1].revents = 0;
+                                       fd_count--;
+                               }
+                               if (count > 0) {
+                                       full_write(1, buf, count);
+                                       full_write(outfd, buf, count);
+                                       if (opt & 4) { /* -f */
+                                               fsync(outfd);
+                                       }
+                               }
+                       }
+               }
+               /* 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(1, buf, count);
+                       full_write(outfd, buf, count);
+               }
+
+               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 */);
+
+       /* signal(SIGCHLD, SIG_DFL); - exec does this for us */
+       execl(shell, shell, shell_opt, shell_arg, 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..1f979a7
--- /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 ATTRIBUTE_UNUSED, char **argv)
+{
+       int pers = -1;
+
+       /* Figure out what personality we are supposed to switch to ...
+        * we can be invoked as either:
+        * argv[0],argv[1] -> "setarch","personality"
+        * argv[0]         -> "personality"
+        */
+retry:
+       if (argv[0][5] == '6') /* linux64 */
+               pers = PER_LINUX;
+       else if (argv[0][5] == '3') /* linux32 */
+               pers = PER_LINUX32;
+       else if (pers == -1 && argv[1] != NULL) {
+               pers = PER_LINUX32;
+               ++argv;
+               goto retry;
+       }
+
+       /* make user actually gave us something to do */
+       ++argv;
+       if (argv[0] == NULL)
+               bb_show_usage();
+
+       /* Try to set personality */
+       if (personality(pers) >= 0) {
+
+               /* Try to execute the program */
+               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..beefac0
--- /dev/null
@@ -0,0 +1,77 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini swapon/swapoff implementation for busybox
+ *
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under the GPL version 2, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <mntent.h>
+#include <sys/swap.h>
+
+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, 0);
+       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 ATTRIBUTE_UNUSED, char **argv)
+{
+       int ret;
+
+       if (!argv[1])
+               bb_show_usage();
+
+       ret = getopt32(argv, "a");
+       if (ret)
+               return do_em_all();
+
+       /* ret = 0; redundant */
+       while (*++argv)
+               ret += swap_enable_disable(*argv);
+       return ret;
+}
diff --git a/util-linux/switch_root.c b/util-linux/switch_root.c
new file mode 100644 (file)
index 0000000..c030b99
--- /dev/null
@@ -0,0 +1,125 @@
+/* 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            0x858458f6
+#endif
+
+#ifndef TMPFS_MAGIC
+#define TMPFS_MAGIC            0x01021994
+#endif
+
+#ifndef MS_MOVE
+#define MS_MOVE                        8192
+#endif
+
+static dev_t rootdev;
+
+// Recursively delete contents of rootfs.
+
+static void delete_contents(const char *directory)
+{
+       DIR *dir;
+       struct dirent *d;
+       struct stat st;
+
+       // Don't descend into other filesystems
+       if (lstat(directory, &st) || st.st_dev != rootdev) return;
+
+       // Recursively delete the contents of directories.
+       if (S_ISDIR(st.st_mode)) {
+               dir = opendir(directory);
+               if (dir) {
+                       while ((d = readdir(dir))) {
+                               char *newdir = d->d_name;
+
+                               // Skip . and ..
+                               if (*newdir=='.' && (!newdir[1] || (newdir[1]=='.' && !newdir[2])))
+                                       continue;
+
+                               // Recurse to delete contents
+                               newdir = alloca(strlen(directory) + strlen(d->d_name) + 2);
+                               sprintf(newdir, "%s/%s", directory, d->d_name);
+                               delete_contents(newdir);
+                       }
+                       closedir(dir);
+
+                       // Directory should now be empty.  Zap it.
+                       rmdir(directory);
+               }
+
+       // It wasn't a directory.  Zap it.
+
+       } else unlink(directory);
+}
+
+int switch_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int switch_root_main(int argc ATTRIBUTE_UNUSED, char **argv)
+{
+       char *newroot, *console = NULL;
+       struct stat st1, st2;
+       struct statfs stfs;
+
+       // 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) ||
+               (stfs.f_type != RAMFS_MAGIC && stfs.f_type != TMPFS_MAGIC) ||
+               getpid() != 1)
+       {
+               bb_error_msg_and_die("not rootfs");
+       }
+
+       // Zap everything out of rootdev
+
+       delete_contents("/");
+
+       // Overmount / with newdir and chroot into it.  The chdir is needed to
+       // recalculate "." and ".." links.
+
+       if (mount(".", "/", NULL, MS_MOVE, NULL))
+               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);
+               dup2(0, 1);
+               dup2(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..6136fa9
--- /dev/null
@@ -0,0 +1,163 @@
+/* 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 <getopt.h>
+#include "libbb.h"
+
+/* 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 ATTRIBUTE_UNUSED, 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("cannot open %s", bb_path_mtab_file);
+       } else {
+               while (getmntent_r(fp, &me, path, PATH_MAX)) {
+                       /* Match fstype if passed */
+                       if (fstype && match_fstype(&me, fstype))
+                               continue;
+                       m = xmalloc(sizeof(struct mtab_list));
+                       m->next = mtl;
+                       m->device = xstrdup(me.mnt_fsname);
+                       m->dir = xstrdup(me.mnt_dir);
+                       mtl = m;
+               }
+               endmntent(fp);
+       }
+
+       // If we're not umounting all, we need at least one argument.
+       if (!(opt & OPT_ALL) && !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 = "cannot remount %s read-only";
+                                       status = EXIT_FAILURE;
+                               }
+                               bb_error_msg(msg, m->device);
+                       } else {
+                               status = EXIT_FAILURE;
+                               bb_perror_msg("cannot %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..54b95f0
--- /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_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..63b0c7c
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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)
+{
+       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..db29dae
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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)
+{
+       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..779971c
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * 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 FAT12_MAX                      0xff5
+#define FAT16_MAX                      0xfff5
+#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;
+       uint8_t         sectors_per_cluster;
+       uint16_t        reserved;
+       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, unsigned count)
+{
+       unsigned i;
+
+       for (i = 0; i < count; i++) {
+               /* end marker */
+               if (dir[i].name[0] == 0x00) {
+                       dbg("end of dir");
+                       break;
+               }
+
+               /* empty entry */
+               if (dir[i].name[0] == FAT_ENTRY_FREE)
+                       continue;
+
+               /* long name */
+               if ((dir[i].attr & FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME)
+                       continue;
+
+               if ((dir[i].attr & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) == FAT_ATTR_VOLUME_ID) {
+                       /* labels do not have file data */
+                       if (dir[i].cluster_high != 0 || dir[i].cluster_low != 0)
+                               continue;
+
+                       dbg("found ATTR_VOLUME_ID id in root dir");
+                       return dir[i].name;
+               }
+
+               dbg("skip dir entry");
+       }
+
+       return NULL;
+}
+
+int volume_id_probe_vfat(struct volume_id *id, uint64_t off)
+{
+       struct vfat_super_block *vs;
+       struct vfat_dir_entry *dir;
+       uint16_t sector_size;
+       uint16_t dir_entries;
+       uint32_t sect_count;
+       uint16_t reserved;
+       uint32_t fat_size;
+       uint32_t root_cluster;
+       uint32_t dir_size;
+       uint32_t cluster_count;
+       uint32_t fat_length;
+       uint64_t root_start;
+       uint32_t start_data_sect;
+       uint16_t root_dir_entries;
+       uint8_t *buf;
+       uint32_t buf_size;
+       uint8_t *label = NULL;
+       uint32_t next;
+       int maxloop;
+
+       dbg("probing at offset 0x%llx", (unsigned long long) off);
+
+       vs = volume_id_get_buffer(id, 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 = le16_to_cpu(vs->sector_size);
+       if (sector_size != 0x200 && sector_size != 0x400 &&
+           sector_size != 0x800 && sector_size != 0x1000)
+               return -1;
+
+       dbg("sector_size 0x%x", sector_size);
+       dbg("sectors_per_cluster 0x%x", vs->sectors_per_cluster);
+
+       dir_entries = le16_to_cpu(vs->dir_entries);
+       reserved = le16_to_cpu(vs->reserved);
+       dbg("reserved 0x%x", reserved);
+
+       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_length = le16_to_cpu(vs->fat_length);
+       if (fat_length == 0)
+               fat_length = le32_to_cpu(vs->type.fat32.fat32_length);
+       dbg("fat_length 0x%x", fat_length);
+
+       fat_size = fat_length * vs->fats;
+       dir_size = ((dir_entries * sizeof(struct vfat_dir_entry)) +
+                       (sector_size-1)) / sector_size;
+       dbg("dir_size 0x%x", dir_size);
+
+       cluster_count = sect_count - (reserved + fat_size + dir_size);
+       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 = (reserved + fat_size) * sector_size;
+       dbg("root dir start 0x%llx", (unsigned long long) root_start);
+       root_dir_entries = le16_to_cpu(vs->dir_entries);
+       dbg("expected entries 0x%x", root_dir_entries);
+
+       buf_size = root_dir_entries * sizeof(struct vfat_dir_entry);
+       buf = volume_id_get_buffer(id, off + root_start, buf_size);
+       if (buf == NULL)
+               goto found;
+
+       dir = (struct vfat_dir_entry*) buf;
+
+       label = get_attr_volume_id(dir, root_dir_entries);
+
+       vs = volume_id_get_buffer(id, 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 found;
+
+ fat32:
+       /* FAT32 root dir is a cluster chain like any other directory */
+       buf_size = vs->sectors_per_cluster * sector_size;
+       root_cluster = le32_to_cpu(vs->type.fat32.root_cluster);
+       dbg("root dir cluster %u", root_cluster);
+       start_data_sect = reserved + fat_size;
+
+       next = root_cluster;
+       maxloop = 100;
+       while (--maxloop) {
+               uint32_t next_sect_off;
+               uint64_t next_off;
+               uint64_t fat_entry_off;
+               int count;
+
+               dbg("next cluster %u", next);
+               next_sect_off = (next - 2) * vs->sectors_per_cluster;
+               next_off = (start_data_sect + next_sect_off) * sector_size;
+               dbg("cluster offset 0x%llx", (unsigned long long) next_off);
+
+               /* get cluster */
+               buf = volume_id_get_buffer(id, off + next_off, buf_size);
+               if (buf == NULL)
+                       goto found;
+
+               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 * sector_size) + (next * sizeof(uint32_t));
+               buf = volume_id_get_buffer(id, off + fat_entry_off, buf_size);
+               if (buf == NULL)
+                       goto found;
+
+               /* set next cluster */
+               next = le32_to_cpu(*((uint32_t *) buf) & 0x0fffffff);
+               if (next == 0)
+                       break;
+       }
+       if (maxloop == 0)
+               dbg("reached maximum follow count of root cluster chain, give up");
+
+       vs = volume_id_get_buffer(id, 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);
+
+ found:
+//     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..b46aad4
--- /dev/null
@@ -0,0 +1,429 @@
+/* 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 "") */
+#if !ENABLE_FEATURE_VOLUMEID_ISO9660
+#define get_label_uuid(device, label, uuid, iso_only) \
+       get_label_uuid(device, label, uuid)
+#endif
+static int
+get_label_uuid(const char *device, char **label, char **uuid, int iso_only)
+{
+       int rv = 1;
+       uint64_t size;
+       struct volume_id *vid;
+
+       vid = volume_id_open_node(device);
+       if (!vid)
+               return rv;
+
+       if (ioctl(vid->fd, BLKGETSIZE64, &size) != 0)
+               size = 0;
+
+#if ENABLE_FEATURE_VOLUMEID_ISO9660
+       if ((iso_only ?
+            volume_id_probe_iso9660(vid, 0) :
+            volume_id_probe_all(vid, 0, size)
+           ) != 0
+       ) {
+               goto ret;
+       }
+#else
+       if (volume_id_probe_all(vid, 0, size) != 0) {
+               goto ret;
+       }
+#endif
+
+       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);
+       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. */
+#if !ENABLE_FEATURE_VOLUMEID_ISO9660
+#define uuidcache_check_device(device_name, ma, mi, iso_only) \
+       uuidcache_check_device(device_name, ma, mi)
+#endif
+static void
+uuidcache_check_device(const char *device_name, int ma, int mi, int iso_only)
+{
+       char *device, *last_slash;
+       char *uuid, *label;
+       char *ptr;
+       int must_remove = 0;
+       int added = 0;
+
+       last_slash = NULL;
+       device = xasprintf("/dev/%s", device_name);
+       if (access(device, F_OK) != 0) {
+               /* device does not exist, temporarily create */
+               int slash_cnt = 0;
+
+               if ((ma | mi) < 0)
+                       goto ret; /* we don't know major:minor! */
+
+               ptr = device;
+               while (*ptr)
+                       if (*ptr++ == '/')
+                               slash_cnt++;
+               if (slash_cnt > 2) {
+// BUG: handles only slash_cnt == 3 case
+                       last_slash = strrchr(device, '/');
+                       *last_slash = '\0';
+                       if (mkdir(device, 0644)) {
+                               bb_perror_msg("can't create directory %s", device);
+                               *last_slash = '/';
+                               last_slash = NULL; /* prevents rmdir */
+                       } else {
+                               *last_slash = '/';
+                       }
+               }
+               mknod(device, S_IFBLK | 0600, makedev(ma, mi));
+               must_remove = 1;
+       }
+
+       uuid = NULL;
+       label = NULL;
+       if (get_label_uuid(device, &label, &uuid, iso_only) == 0) {
+               uuidcache_addentry(device, /*ma, mi,*/ label, uuid);
+               /* "device" is owned by cache now, don't free */
+               added = 1;
+       }
+
+       if (must_remove)
+               unlink(device);
+       if (last_slash) {
+               *last_slash = '\0';
+               rmdir(device);
+       }
+ ret:
+       if (!added)
+               free(device);
+}
+
+/* Run uuidcache_check_device() for every device mentioned
+ * in /proc/partitions */
+static void
+uuidcache_init_partitions(void)
+{
+       char line[100];
+       int ma, mi;
+       unsigned long long sz;
+       FILE *procpt;
+       int firstPass;
+       int handleOnFirst;
+       char *chptr;
+
+       procpt = xfopen("/proc/partitions", "r");
+/*
+# cat /proc/partitions
+major minor  #blocks  name
+
+   8     0  293036184 sda
+   8     1    6835626 sda1
+   8     2          1 sda2
+   8     5     979933 sda5
+   8     6   15623181 sda6
+   8     7   97659103 sda7
+   8     8  171935631 sda8
+*/
+       for (firstPass = 1; firstPass >= 0; firstPass--) {
+               fseek(procpt, 0, SEEK_SET);
+
+               while (fgets(line, sizeof(line), procpt)) {
+                       /* The original version of this code used sscanf, but
+                          diet's sscanf is quite limited */
+                       chptr = line;
+                       if (*chptr != ' ') continue;
+                       chptr = skip_whitespace(chptr);
+
+                       ma = bb_strtou(chptr, &chptr, 0);
+                       if (ma < 0) continue;
+                       chptr = skip_whitespace(chptr);
+
+                       mi = bb_strtou(chptr, &chptr, 0);
+                       if (mi < 0) continue;
+                       chptr = skip_whitespace(chptr);
+
+                       sz = bb_strtoull(chptr, &chptr, 0);
+                       if ((long long)sz == -1LL) continue;
+                       chptr = skip_whitespace(chptr);
+
+                       /* skip extended partitions (heuristic: size 1) */
+                       if (sz == 1)
+                               continue;
+
+                       *strchrnul(chptr, '\n') = '\0';
+                       /* now chptr => device name */
+                       dbg("/proc/partitions: maj:%d min:%d sz:%llu name:'%s'",
+                                               ma, mi, sz, chptr);
+                       if (!chptr[0])
+                               continue;
+
+                       /* look only at md devices on first pass */
+                       handleOnFirst = (chptr[0] == 'm' && chptr[1] == 'd');
+                       if (firstPass != handleOnFirst)
+                               continue;
+
+                       /* heuristic: partition name ends in a digit */
+                       if (isdigit(chptr[strlen(chptr) - 1])) {
+                               uuidcache_check_device(chptr, ma, mi, 0);
+                       }
+               }
+       }
+
+       fclose(procpt);
+}
+
+static void
+dev_get_major_minor(char *device_name, int *major, int *minor)
+{
+       char dev[16];
+       char *dev_path;
+       char *colon;
+       int sz;
+
+       dev_path = xasprintf("/sys/block/%s/dev", device_name);
+       sz = open_read_close(dev_path, dev, sizeof(dev) - 1);
+       if (sz < 0)
+               goto ret;
+       dev[sz] = '\0';
+
+       colon = strchr(dev, ':');
+       if (!colon)
+               goto ret;
+       *major = bb_strtou(dev, NULL, 10);
+       *minor = bb_strtou(colon + 1, NULL, 10);
+
+ ret:
+       free(dev_path);
+       return;
+}
+
+static void
+uuidcache_init_cdroms(void)
+{
+#define PROC_CDROMS "/proc/sys/dev/cdrom/info"
+       char line[100];
+       int ma, mi;
+       FILE *proccd;
+
+       proccd = fopen(PROC_CDROMS, "r");
+       if (!proccd) {
+//             static smallint warn = 0;
+//             if (!warn) {
+//                     warn = 1;
+//                     bb_error_msg("can't open %s, UUID and LABEL "
+//                             "conversion cannot be done for CD-Roms",
+//                             PROC_CDROMS);
+//             }
+               return;
+       }
+
+       while (fgets(line, sizeof(line), proccd)) {
+               static const char drive_name_string[] ALIGN1 = "drive name:";
+
+               if (strncmp(line, drive_name_string, sizeof(drive_name_string) - 1) == 0) {
+                       char *device_name;
+
+                       device_name = strtok(skip_whitespace(line + sizeof(drive_name_string) - 1), " \t\n");
+                       while (device_name && device_name[0]) {
+                               ma = mi = -1;
+                               dev_get_major_minor(device_name, &ma, &mi);
+                               uuidcache_check_device(device_name, ma, mi, 1);
+                               device_name = strtok(NULL, " \t\n");
+                       }
+                       break;
+               }
+       }
+
+       fclose(proccd);
+}
+
+static void
+uuidcache_init(void)
+{
+       if (uuidCache)
+               return;
+
+       uuidcache_init_partitions();
+       uuidcache_init_cdroms();
+}
+
+#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);
+}
+
+static int display_uuid_cache(void)
+{
+       struct uuidCache_s *u;
+       size_t i;
+
+       uuidcache_init();
+
+       u = uuidCache;
+       while (u) {
+               printf("%s %s %s\n", u->device, u->label, u->uc_uuid);
+               u = u->next;
+       }
+
+       return 0;
+}
+#endif // UNUSED
+
+
+/* Used by mount and findfs */
+
+char *get_devname_from_label(const char *spec)
+{
+       struct uuidCache_s *uc;
+       int spec_len = strlen(spec);
+
+       uuidcache_init();
+       uc = uuidCache;
+       while (uc) {
+// FIXME: empty label ("LABEL=") matches anything??!
+               if (uc->label[0] && strncmp(spec, uc->label, spec_len) == 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..a7667f7
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * 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)
+{
+       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..c15608c
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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)
+{
+       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..63692f9
--- /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 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)
+{
+       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..a113060
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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)
+{
+       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;
+
+       memcpy(uuid, &mdp->set_uuid0, 4);
+       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..e608454
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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)
+{
+       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..51dda4e
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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 SECTOR_SHIFT                   9
+#define SECTOR_SIZE                    (1 << SECTOR_SHIFT)
+
+#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 };
+#define LUKS_MAGIC_L 6
+#define LUKS_PHDR_SIZE (sizeof(struct luks_phdr)/SECTOR_SIZE+1)
+#define UUID_STRING_L 40
+
+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];
+};
+
+int volume_id_probe_luks(struct volume_id *id, uint64_t off)
+{
+       struct luks_phdr *header;
+
+       header = volume_id_get_buffer(id, off, LUKS_PHDR_SIZE);
+       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..7488a41
--- /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 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)
+{
+       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..8bcaac0
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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)
+{
+       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..d9a3745
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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)
+{
+       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..400bdce
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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)
+{
+       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..7671962
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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)
+{
+       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..55e97a7
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * 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)
+{
+       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..ba76876
--- /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;
+       int 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..ce7de23
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * 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;
+       }
+}
+
+void *volume_id_get_buffer(struct volume_id *id, uint64_t off, size_t len)
+{
+       ssize_t buf_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) {
+               if (id->sbbuf == NULL) {
+                       id->sbbuf = xmalloc(SB_BUFFER_SIZE);
+               }
+
+               /* check if we need to read */
+               if ((off + len) > id->sbbuf_len) {
+                       dbg("read sbbuf len:0x%llx", (unsigned long long) (off + len));
+                       xlseek(id->fd, 0, SEEK_SET);
+                       buf_len = full_read(id->fd, id->sbbuf, off + len);
+                       if (buf_len < 0) {
+                               dbg("read failed (%s)", strerror(errno));
+                               return NULL;
+                       }
+                       dbg("got 0x%zx (%zi) bytes", buf_len, buf_len);
+                       id->sbbuf_len = buf_len;
+                       if (buf_len < off + len) {
+                               dbg("requested 0x%zx bytes, got only 0x%zx bytes", len, buf_len);
+                               return NULL;
+                       }
+               }
+
+               return &(id->sbbuf[off]);
+       }
+
+       if (len > SEEK_BUFFER_SIZE) {
+               dbg("seek buffer too small %d", SEEK_BUFFER_SIZE);
+               return NULL;
+       }
+
+       /* get seek buffer */
+       if (id->seekbuf == NULL) {
+               id->seekbuf = xmalloc(SEEK_BUFFER_SIZE);
+       }
+
+       /* check if we need to read */
+       if ((off < id->seekbuf_off) || ((off + len) > (id->seekbuf_off + id->seekbuf_len))) {
+               dbg("read seekbuf off:0x%llx len:0x%zx", (unsigned long long) off, len);
+               xlseek(id->fd, off, SEEK_SET);
+               buf_len = full_read(id->fd, id->seekbuf, len);
+               if (buf_len < 0) {
+                       dbg("read failed (%s)", strerror(errno));
+                       return NULL;
+               }
+               dbg("got 0x%zx (%zi) bytes", buf_len, buf_len);
+               id->seekbuf_off = off;
+               id->seekbuf_len = buf_len;
+               if (buf_len < len) {
+                       dbg("requested 0x%zx bytes, got only 0x%zx bytes", len, buf_len);
+                       return NULL;
+               }
+       }
+
+       return &(id->seekbuf[off - id->seekbuf_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;
+}
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..de9aae2
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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)
+{
+       int i;
+
+       if (id == NULL)
+               return -EINVAL;
+
+       /* 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;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(raid2); i++)
+               if (raid2[i](id, off) == 0)
+                       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;
+
+       /* 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;
+       return -1;
+
+ ret:
+       /* If the filestystem in recognized, we free the allocated buffers,
+          otherwise they will stay in place for the possible next probe call */
+       volume_id_free_buffer(id);
+
+       return 0;
+}
+
+/* open volume by device node */
+struct volume_id *volume_id_open_node(const char *path)
+{
+       struct volume_id *id;
+       int fd;
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return NULL;
+       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..78b4a7b
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * 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"
+
+
+#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 {
+//     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;
+
+#ifdef UNUSED_PARTITION_CODE
+       struct volume_id_partition *partitions;
+       size_t          partition_count;
+#endif
+
+       int             fd;
+       uint8_t         *sbbuf;
+       uint8_t         *seekbuf;
+       size_t          sbbuf_len;
+       uint64_t        seekbuf_off;
+       size_t          seekbuf_len;
+//     int             fd_close:1;
+};
+
+struct volume_id *volume_id_open_node(const char *path);
+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);
diff --git a/util-linux/volume_id/xfs.c b/util-linux/volume_id/xfs.c
new file mode 100644 (file)
index 0000000..0d90437
--- /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 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)
+{
+       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;
+}